##// END OF EJS Templates
commits/pr pages various fixes....
marcink -
r4485:ac1b264f default
parent child Browse files
Show More

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

@@ -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
@@ -1,507 +1,494 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import pytest
21 import pytest
22
22
23 from rhodecode.tests import TestController
23 from rhodecode.tests import TestController
24
24
25 from rhodecode.model.db import ChangesetComment, Notification
25 from rhodecode.model.db import ChangesetComment, Notification
26 from rhodecode.model.meta import Session
26 from rhodecode.model.meta import Session
27 from rhodecode.lib import helpers as h
27 from rhodecode.lib import helpers as h
28
28
29
29
30 def route_path(name, params=None, **kwargs):
30 def route_path(name, params=None, **kwargs):
31 import urllib
31 import urllib
32
32
33 base_url = {
33 base_url = {
34 'repo_commit': '/{repo_name}/changeset/{commit_id}',
34 'repo_commit': '/{repo_name}/changeset/{commit_id}',
35 'repo_commit_comment_create': '/{repo_name}/changeset/{commit_id}/comment/create',
35 'repo_commit_comment_create': '/{repo_name}/changeset/{commit_id}/comment/create',
36 'repo_commit_comment_preview': '/{repo_name}/changeset/{commit_id}/comment/preview',
36 'repo_commit_comment_preview': '/{repo_name}/changeset/{commit_id}/comment/preview',
37 'repo_commit_comment_delete': '/{repo_name}/changeset/{commit_id}/comment/{comment_id}/delete',
37 'repo_commit_comment_delete': '/{repo_name}/changeset/{commit_id}/comment/{comment_id}/delete',
38 'repo_commit_comment_edit': '/{repo_name}/changeset/{commit_id}/comment/{comment_id}/edit',
38 'repo_commit_comment_edit': '/{repo_name}/changeset/{commit_id}/comment/{comment_id}/edit',
39 }[name].format(**kwargs)
39 }[name].format(**kwargs)
40
40
41 if params:
41 if params:
42 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
42 base_url = '{}?{}'.format(base_url, urllib.urlencode(params))
43 return base_url
43 return base_url
44
44
45
45
46 @pytest.mark.backends("git", "hg", "svn")
46 @pytest.mark.backends("git", "hg", "svn")
47 class TestRepoCommitCommentsView(TestController):
47 class TestRepoCommitCommentsView(TestController):
48
48
49 @pytest.fixture(autouse=True)
49 @pytest.fixture(autouse=True)
50 def prepare(self, request, baseapp):
50 def prepare(self, request, baseapp):
51 for x in ChangesetComment.query().all():
51 for x in ChangesetComment.query().all():
52 Session().delete(x)
52 Session().delete(x)
53 Session().commit()
53 Session().commit()
54
54
55 for x in Notification.query().all():
55 for x in Notification.query().all():
56 Session().delete(x)
56 Session().delete(x)
57 Session().commit()
57 Session().commit()
58
58
59 request.addfinalizer(self.cleanup)
59 request.addfinalizer(self.cleanup)
60
60
61 def cleanup(self):
61 def cleanup(self):
62 for x in ChangesetComment.query().all():
62 for x in ChangesetComment.query().all():
63 Session().delete(x)
63 Session().delete(x)
64 Session().commit()
64 Session().commit()
65
65
66 for x in Notification.query().all():
66 for x in Notification.query().all():
67 Session().delete(x)
67 Session().delete(x)
68 Session().commit()
68 Session().commit()
69
69
70 @pytest.mark.parametrize('comment_type', ChangesetComment.COMMENT_TYPES)
70 @pytest.mark.parametrize('comment_type', ChangesetComment.COMMENT_TYPES)
71 def test_create(self, comment_type, backend):
71 def test_create(self, comment_type, backend):
72 self.log_user()
72 self.log_user()
73 commit = backend.repo.get_commit('300')
73 commit = backend.repo.get_commit('300')
74 commit_id = commit.raw_id
74 commit_id = commit.raw_id
75 text = u'CommentOnCommit'
75 text = u'CommentOnCommit'
76
76
77 params = {'text': text, 'csrf_token': self.csrf_token,
77 params = {'text': text, 'csrf_token': self.csrf_token,
78 'comment_type': comment_type}
78 'comment_type': comment_type}
79 self.app.post(
79 self.app.post(
80 route_path('repo_commit_comment_create',
80 route_path('repo_commit_comment_create',
81 repo_name=backend.repo_name, commit_id=commit_id),
81 repo_name=backend.repo_name, commit_id=commit_id),
82 params=params)
82 params=params)
83
83
84 response = self.app.get(
84 response = self.app.get(
85 route_path('repo_commit',
85 route_path('repo_commit',
86 repo_name=backend.repo_name, commit_id=commit_id))
86 repo_name=backend.repo_name, commit_id=commit_id))
87
87
88 # test DB
88 # test DB
89 assert ChangesetComment.query().count() == 1
89 assert ChangesetComment.query().count() == 1
90 assert_comment_links(response, ChangesetComment.query().count(), 0)
90 assert_comment_links(response, ChangesetComment.query().count(), 0)
91
91
92 assert Notification.query().count() == 1
92 assert Notification.query().count() == 1
93 assert ChangesetComment.query().count() == 1
93 assert ChangesetComment.query().count() == 1
94
94
95 notification = Notification.query().all()[0]
95 notification = Notification.query().all()[0]
96
96
97 comment_id = ChangesetComment.query().first().comment_id
97 comment_id = ChangesetComment.query().first().comment_id
98 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
98 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
99
99
100 author = notification.created_by_user.username_and_name
100 author = notification.created_by_user.username_and_name
101 sbj = '@{0} left a {1} on commit `{2}` in the `{3}` repository'.format(
101 sbj = '@{0} left a {1} on commit `{2}` in the `{3}` repository'.format(
102 author, comment_type, h.show_id(commit), backend.repo_name)
102 author, comment_type, h.show_id(commit), backend.repo_name)
103 assert sbj == notification.subject
103 assert sbj == notification.subject
104
104
105 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
105 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
106 backend.repo_name, commit_id, comment_id))
106 backend.repo_name, commit_id, comment_id))
107 assert lnk in notification.body
107 assert lnk in notification.body
108
108
109 @pytest.mark.parametrize('comment_type', ChangesetComment.COMMENT_TYPES)
109 @pytest.mark.parametrize('comment_type', ChangesetComment.COMMENT_TYPES)
110 def test_create_inline(self, comment_type, backend):
110 def test_create_inline(self, comment_type, backend):
111 self.log_user()
111 self.log_user()
112 commit = backend.repo.get_commit('300')
112 commit = backend.repo.get_commit('300')
113 commit_id = commit.raw_id
113 commit_id = commit.raw_id
114 text = u'CommentOnCommit'
114 text = u'CommentOnCommit'
115 f_path = 'vcs/web/simplevcs/views/repository.py'
115 f_path = 'vcs/web/simplevcs/views/repository.py'
116 line = 'n1'
116 line = 'n1'
117
117
118 params = {'text': text, 'f_path': f_path, 'line': line,
118 params = {'text': text, 'f_path': f_path, 'line': line,
119 'comment_type': comment_type,
119 'comment_type': comment_type,
120 'csrf_token': self.csrf_token}
120 'csrf_token': self.csrf_token}
121
121
122 self.app.post(
122 self.app.post(
123 route_path('repo_commit_comment_create',
123 route_path('repo_commit_comment_create',
124 repo_name=backend.repo_name, commit_id=commit_id),
124 repo_name=backend.repo_name, commit_id=commit_id),
125 params=params)
125 params=params)
126
126
127 response = self.app.get(
127 response = self.app.get(
128 route_path('repo_commit',
128 route_path('repo_commit',
129 repo_name=backend.repo_name, commit_id=commit_id))
129 repo_name=backend.repo_name, commit_id=commit_id))
130
130
131 # test DB
131 # test DB
132 assert ChangesetComment.query().count() == 1
132 assert ChangesetComment.query().count() == 1
133 assert_comment_links(response, 0, ChangesetComment.query().count())
133 assert_comment_links(response, 0, ChangesetComment.query().count())
134
134
135 if backend.alias == 'svn':
135 if backend.alias == 'svn':
136 response.mustcontain(
136 response.mustcontain(
137 '''data-f-path="vcs/commands/summary.py" '''
137 '''data-f-path="vcs/commands/summary.py" '''
138 '''data-anchor-id="c-300-ad05457a43f8"'''
138 '''data-anchor-id="c-300-ad05457a43f8"'''
139 )
139 )
140 if backend.alias == 'git':
140 if backend.alias == 'git':
141 response.mustcontain(
141 response.mustcontain(
142 '''data-f-path="vcs/backends/hg.py" '''
142 '''data-f-path="vcs/backends/hg.py" '''
143 '''data-anchor-id="c-883e775e89ea-9c390eb52cd6"'''
143 '''data-anchor-id="c-883e775e89ea-9c390eb52cd6"'''
144 )
144 )
145
145
146 if backend.alias == 'hg':
146 if backend.alias == 'hg':
147 response.mustcontain(
147 response.mustcontain(
148 '''data-f-path="vcs/backends/hg.py" '''
148 '''data-f-path="vcs/backends/hg.py" '''
149 '''data-anchor-id="c-e58d85a3973b-9c390eb52cd6"'''
149 '''data-anchor-id="c-e58d85a3973b-9c390eb52cd6"'''
150 )
150 )
151
151
152 assert Notification.query().count() == 1
152 assert Notification.query().count() == 1
153 assert ChangesetComment.query().count() == 1
153 assert ChangesetComment.query().count() == 1
154
154
155 notification = Notification.query().all()[0]
155 notification = Notification.query().all()[0]
156 comment = ChangesetComment.query().first()
156 comment = ChangesetComment.query().first()
157 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
157 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
158
158
159 assert comment.revision == commit_id
159 assert comment.revision == commit_id
160
160
161 author = notification.created_by_user.username_and_name
161 author = notification.created_by_user.username_and_name
162 sbj = '@{0} left a {1} on file `{2}` in commit `{3}` in the `{4}` repository'.format(
162 sbj = '@{0} left a {1} on file `{2}` in commit `{3}` in the `{4}` repository'.format(
163 author, comment_type, f_path, h.show_id(commit), backend.repo_name)
163 author, comment_type, f_path, h.show_id(commit), backend.repo_name)
164
164
165 assert sbj == notification.subject
165 assert sbj == notification.subject
166
166
167 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
167 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
168 backend.repo_name, commit_id, comment.comment_id))
168 backend.repo_name, commit_id, comment.comment_id))
169 assert lnk in notification.body
169 assert lnk in notification.body
170 assert 'on line n1' in notification.body
170 assert 'on line n1' in notification.body
171
171
172 def test_create_with_mention(self, backend):
172 def test_create_with_mention(self, backend):
173 self.log_user()
173 self.log_user()
174
174
175 commit_id = backend.repo.get_commit('300').raw_id
175 commit_id = backend.repo.get_commit('300').raw_id
176 text = u'@test_regular check CommentOnCommit'
176 text = u'@test_regular check CommentOnCommit'
177
177
178 params = {'text': text, 'csrf_token': self.csrf_token}
178 params = {'text': text, 'csrf_token': self.csrf_token}
179 self.app.post(
179 self.app.post(
180 route_path('repo_commit_comment_create',
180 route_path('repo_commit_comment_create',
181 repo_name=backend.repo_name, commit_id=commit_id),
181 repo_name=backend.repo_name, commit_id=commit_id),
182 params=params)
182 params=params)
183
183
184 response = self.app.get(
184 response = self.app.get(
185 route_path('repo_commit',
185 route_path('repo_commit',
186 repo_name=backend.repo_name, commit_id=commit_id))
186 repo_name=backend.repo_name, commit_id=commit_id))
187 # test DB
187 # test DB
188 assert ChangesetComment.query().count() == 1
188 assert ChangesetComment.query().count() == 1
189 assert_comment_links(response, ChangesetComment.query().count(), 0)
189 assert_comment_links(response, ChangesetComment.query().count(), 0)
190
190
191 notification = Notification.query().one()
191 notification = Notification.query().one()
192
192
193 assert len(notification.recipients) == 2
193 assert len(notification.recipients) == 2
194 users = [x.username for x in notification.recipients]
194 users = [x.username for x in notification.recipients]
195
195
196 # test_regular gets notification by @mention
196 # test_regular gets notification by @mention
197 assert sorted(users) == [u'test_admin', u'test_regular']
197 assert sorted(users) == [u'test_admin', u'test_regular']
198
198
199 def test_create_with_status_change(self, backend):
199 def test_create_with_status_change(self, backend):
200 self.log_user()
200 self.log_user()
201 commit = backend.repo.get_commit('300')
201 commit = backend.repo.get_commit('300')
202 commit_id = commit.raw_id
202 commit_id = commit.raw_id
203 text = u'CommentOnCommit'
203 text = u'CommentOnCommit'
204 f_path = 'vcs/web/simplevcs/views/repository.py'
204 f_path = 'vcs/web/simplevcs/views/repository.py'
205 line = 'n1'
205 line = 'n1'
206
206
207 params = {'text': text, 'changeset_status': 'approved',
207 params = {'text': text, 'changeset_status': 'approved',
208 'csrf_token': self.csrf_token}
208 'csrf_token': self.csrf_token}
209
209
210 self.app.post(
210 self.app.post(
211 route_path(
211 route_path(
212 'repo_commit_comment_create',
212 'repo_commit_comment_create',
213 repo_name=backend.repo_name, commit_id=commit_id),
213 repo_name=backend.repo_name, commit_id=commit_id),
214 params=params)
214 params=params)
215
215
216 response = self.app.get(
216 response = self.app.get(
217 route_path('repo_commit',
217 route_path('repo_commit',
218 repo_name=backend.repo_name, commit_id=commit_id))
218 repo_name=backend.repo_name, commit_id=commit_id))
219
219
220 # test DB
220 # test DB
221 assert ChangesetComment.query().count() == 1
221 assert ChangesetComment.query().count() == 1
222 assert_comment_links(response, ChangesetComment.query().count(), 0)
222 assert_comment_links(response, ChangesetComment.query().count(), 0)
223
223
224 assert Notification.query().count() == 1
224 assert Notification.query().count() == 1
225 assert ChangesetComment.query().count() == 1
225 assert ChangesetComment.query().count() == 1
226
226
227 notification = Notification.query().all()[0]
227 notification = Notification.query().all()[0]
228
228
229 comment_id = ChangesetComment.query().first().comment_id
229 comment_id = ChangesetComment.query().first().comment_id
230 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
230 assert notification.type_ == Notification.TYPE_CHANGESET_COMMENT
231
231
232 author = notification.created_by_user.username_and_name
232 author = notification.created_by_user.username_and_name
233 sbj = '[status: Approved] @{0} left a note on commit `{1}` in the `{2}` repository'.format(
233 sbj = '[status: Approved] @{0} left a note on commit `{1}` in the `{2}` repository'.format(
234 author, h.show_id(commit), backend.repo_name)
234 author, h.show_id(commit), backend.repo_name)
235 assert sbj == notification.subject
235 assert sbj == notification.subject
236
236
237 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
237 lnk = (u'/{0}/changeset/{1}#comment-{2}'.format(
238 backend.repo_name, commit_id, comment_id))
238 backend.repo_name, commit_id, comment_id))
239 assert lnk in notification.body
239 assert lnk in notification.body
240
240
241 def test_delete(self, backend):
241 def test_delete(self, backend):
242 self.log_user()
242 self.log_user()
243 commit_id = backend.repo.get_commit('300').raw_id
243 commit_id = backend.repo.get_commit('300').raw_id
244 text = u'CommentOnCommit'
244 text = u'CommentOnCommit'
245
245
246 params = {'text': text, 'csrf_token': self.csrf_token}
246 params = {'text': text, 'csrf_token': self.csrf_token}
247 self.app.post(
247 self.app.post(
248 route_path(
248 route_path(
249 'repo_commit_comment_create',
249 'repo_commit_comment_create',
250 repo_name=backend.repo_name, commit_id=commit_id),
250 repo_name=backend.repo_name, commit_id=commit_id),
251 params=params)
251 params=params)
252
252
253 comments = ChangesetComment.query().all()
253 comments = ChangesetComment.query().all()
254 assert len(comments) == 1
254 assert len(comments) == 1
255 comment_id = comments[0].comment_id
255 comment_id = comments[0].comment_id
256
256
257 self.app.post(
257 self.app.post(
258 route_path('repo_commit_comment_delete',
258 route_path('repo_commit_comment_delete',
259 repo_name=backend.repo_name,
259 repo_name=backend.repo_name,
260 commit_id=commit_id,
260 commit_id=commit_id,
261 comment_id=comment_id),
261 comment_id=comment_id),
262 params={'csrf_token': self.csrf_token})
262 params={'csrf_token': self.csrf_token})
263
263
264 comments = ChangesetComment.query().all()
264 comments = ChangesetComment.query().all()
265 assert len(comments) == 0
265 assert len(comments) == 0
266
266
267 response = self.app.get(
267 response = self.app.get(
268 route_path('repo_commit',
268 route_path('repo_commit',
269 repo_name=backend.repo_name, commit_id=commit_id))
269 repo_name=backend.repo_name, commit_id=commit_id))
270 assert_comment_links(response, 0, 0)
270 assert_comment_links(response, 0, 0)
271
271
272 def test_edit(self, backend):
272 def test_edit(self, backend):
273 self.log_user()
273 self.log_user()
274 commit_id = backend.repo.get_commit('300').raw_id
274 commit_id = backend.repo.get_commit('300').raw_id
275 text = u'CommentOnCommit'
275 text = u'CommentOnCommit'
276
276
277 params = {'text': text, 'csrf_token': self.csrf_token}
277 params = {'text': text, 'csrf_token': self.csrf_token}
278 self.app.post(
278 self.app.post(
279 route_path(
279 route_path(
280 'repo_commit_comment_create',
280 'repo_commit_comment_create',
281 repo_name=backend.repo_name, commit_id=commit_id),
281 repo_name=backend.repo_name, commit_id=commit_id),
282 params=params)
282 params=params)
283
283
284 comments = ChangesetComment.query().all()
284 comments = ChangesetComment.query().all()
285 assert len(comments) == 1
285 assert len(comments) == 1
286 comment_id = comments[0].comment_id
286 comment_id = comments[0].comment_id
287 test_text = 'test_text'
287 test_text = 'test_text'
288 self.app.post(
288 self.app.post(
289 route_path(
289 route_path(
290 'repo_commit_comment_edit',
290 'repo_commit_comment_edit',
291 repo_name=backend.repo_name,
291 repo_name=backend.repo_name,
292 commit_id=commit_id,
292 commit_id=commit_id,
293 comment_id=comment_id,
293 comment_id=comment_id,
294 ),
294 ),
295 params={
295 params={
296 'csrf_token': self.csrf_token,
296 'csrf_token': self.csrf_token,
297 'text': test_text,
297 'text': test_text,
298 'version': '0',
298 'version': '0',
299 })
299 })
300
300
301 text_form_db = ChangesetComment.query().filter(
301 text_form_db = ChangesetComment.query().filter(
302 ChangesetComment.comment_id == comment_id).first().text
302 ChangesetComment.comment_id == comment_id).first().text
303 assert test_text == text_form_db
303 assert test_text == text_form_db
304
304
305 def test_edit_without_change(self, backend):
305 def test_edit_without_change(self, backend):
306 self.log_user()
306 self.log_user()
307 commit_id = backend.repo.get_commit('300').raw_id
307 commit_id = backend.repo.get_commit('300').raw_id
308 text = u'CommentOnCommit'
308 text = u'CommentOnCommit'
309
309
310 params = {'text': text, 'csrf_token': self.csrf_token}
310 params = {'text': text, 'csrf_token': self.csrf_token}
311 self.app.post(
311 self.app.post(
312 route_path(
312 route_path(
313 'repo_commit_comment_create',
313 'repo_commit_comment_create',
314 repo_name=backend.repo_name, commit_id=commit_id),
314 repo_name=backend.repo_name, commit_id=commit_id),
315 params=params)
315 params=params)
316
316
317 comments = ChangesetComment.query().all()
317 comments = ChangesetComment.query().all()
318 assert len(comments) == 1
318 assert len(comments) == 1
319 comment_id = comments[0].comment_id
319 comment_id = comments[0].comment_id
320
320
321 response = self.app.post(
321 response = self.app.post(
322 route_path(
322 route_path(
323 'repo_commit_comment_edit',
323 'repo_commit_comment_edit',
324 repo_name=backend.repo_name,
324 repo_name=backend.repo_name,
325 commit_id=commit_id,
325 commit_id=commit_id,
326 comment_id=comment_id,
326 comment_id=comment_id,
327 ),
327 ),
328 params={
328 params={
329 'csrf_token': self.csrf_token,
329 'csrf_token': self.csrf_token,
330 'text': text,
330 'text': text,
331 'version': '0',
331 'version': '0',
332 },
332 },
333 status=404,
333 status=404,
334 )
334 )
335 assert response.status_int == 404
335 assert response.status_int == 404
336
336
337 def test_edit_try_edit_already_edited(self, backend):
337 def test_edit_try_edit_already_edited(self, backend):
338 self.log_user()
338 self.log_user()
339 commit_id = backend.repo.get_commit('300').raw_id
339 commit_id = backend.repo.get_commit('300').raw_id
340 text = u'CommentOnCommit'
340 text = u'CommentOnCommit'
341
341
342 params = {'text': text, 'csrf_token': self.csrf_token}
342 params = {'text': text, 'csrf_token': self.csrf_token}
343 self.app.post(
343 self.app.post(
344 route_path(
344 route_path(
345 'repo_commit_comment_create',
345 'repo_commit_comment_create',
346 repo_name=backend.repo_name, commit_id=commit_id
346 repo_name=backend.repo_name, commit_id=commit_id
347 ),
347 ),
348 params=params,
348 params=params,
349 )
349 )
350
350
351 comments = ChangesetComment.query().all()
351 comments = ChangesetComment.query().all()
352 assert len(comments) == 1
352 assert len(comments) == 1
353 comment_id = comments[0].comment_id
353 comment_id = comments[0].comment_id
354 test_text = 'test_text'
354 test_text = 'test_text'
355 self.app.post(
355 self.app.post(
356 route_path(
356 route_path(
357 'repo_commit_comment_edit',
357 'repo_commit_comment_edit',
358 repo_name=backend.repo_name,
358 repo_name=backend.repo_name,
359 commit_id=commit_id,
359 commit_id=commit_id,
360 comment_id=comment_id,
360 comment_id=comment_id,
361 ),
361 ),
362 params={
362 params={
363 'csrf_token': self.csrf_token,
363 'csrf_token': self.csrf_token,
364 'text': test_text,
364 'text': test_text,
365 'version': '0',
365 'version': '0',
366 }
366 }
367 )
367 )
368 test_text_v2 = 'test_v2'
368 test_text_v2 = 'test_v2'
369 response = self.app.post(
369 response = self.app.post(
370 route_path(
370 route_path(
371 'repo_commit_comment_edit',
371 'repo_commit_comment_edit',
372 repo_name=backend.repo_name,
372 repo_name=backend.repo_name,
373 commit_id=commit_id,
373 commit_id=commit_id,
374 comment_id=comment_id,
374 comment_id=comment_id,
375 ),
375 ),
376 params={
376 params={
377 'csrf_token': self.csrf_token,
377 'csrf_token': self.csrf_token,
378 'text': test_text_v2,
378 'text': test_text_v2,
379 'version': '0',
379 'version': '0',
380 },
380 },
381 status=409,
381 status=409,
382 )
382 )
383 assert response.status_int == 409
383 assert response.status_int == 409
384
384
385 text_form_db = ChangesetComment.query().filter(
385 text_form_db = ChangesetComment.query().filter(
386 ChangesetComment.comment_id == comment_id).first().text
386 ChangesetComment.comment_id == comment_id).first().text
387
387
388 assert test_text == text_form_db
388 assert test_text == text_form_db
389 assert test_text_v2 != text_form_db
389 assert test_text_v2 != text_form_db
390
390
391 def test_edit_forbidden_for_immutable_comments(self, backend):
391 def test_edit_forbidden_for_immutable_comments(self, backend):
392 self.log_user()
392 self.log_user()
393 commit_id = backend.repo.get_commit('300').raw_id
393 commit_id = backend.repo.get_commit('300').raw_id
394 text = u'CommentOnCommit'
394 text = u'CommentOnCommit'
395
395
396 params = {'text': text, 'csrf_token': self.csrf_token, 'version': '0'}
396 params = {'text': text, 'csrf_token': self.csrf_token, 'version': '0'}
397 self.app.post(
397 self.app.post(
398 route_path(
398 route_path(
399 'repo_commit_comment_create',
399 'repo_commit_comment_create',
400 repo_name=backend.repo_name,
400 repo_name=backend.repo_name,
401 commit_id=commit_id,
401 commit_id=commit_id,
402 ),
402 ),
403 params=params
403 params=params
404 )
404 )
405
405
406 comments = ChangesetComment.query().all()
406 comments = ChangesetComment.query().all()
407 assert len(comments) == 1
407 assert len(comments) == 1
408 comment_id = comments[0].comment_id
408 comment_id = comments[0].comment_id
409
409
410 comment = ChangesetComment.get(comment_id)
410 comment = ChangesetComment.get(comment_id)
411 comment.immutable_state = ChangesetComment.OP_IMMUTABLE
411 comment.immutable_state = ChangesetComment.OP_IMMUTABLE
412 Session().add(comment)
412 Session().add(comment)
413 Session().commit()
413 Session().commit()
414
414
415 response = self.app.post(
415 response = self.app.post(
416 route_path(
416 route_path(
417 'repo_commit_comment_edit',
417 'repo_commit_comment_edit',
418 repo_name=backend.repo_name,
418 repo_name=backend.repo_name,
419 commit_id=commit_id,
419 commit_id=commit_id,
420 comment_id=comment_id,
420 comment_id=comment_id,
421 ),
421 ),
422 params={
422 params={
423 'csrf_token': self.csrf_token,
423 'csrf_token': self.csrf_token,
424 'text': 'test_text',
424 'text': 'test_text',
425 },
425 },
426 status=403,
426 status=403,
427 )
427 )
428 assert response.status_int == 403
428 assert response.status_int == 403
429
429
430 def test_delete_forbidden_for_immutable_comments(self, backend):
430 def test_delete_forbidden_for_immutable_comments(self, backend):
431 self.log_user()
431 self.log_user()
432 commit_id = backend.repo.get_commit('300').raw_id
432 commit_id = backend.repo.get_commit('300').raw_id
433 text = u'CommentOnCommit'
433 text = u'CommentOnCommit'
434
434
435 params = {'text': text, 'csrf_token': self.csrf_token}
435 params = {'text': text, 'csrf_token': self.csrf_token}
436 self.app.post(
436 self.app.post(
437 route_path(
437 route_path(
438 'repo_commit_comment_create',
438 'repo_commit_comment_create',
439 repo_name=backend.repo_name, commit_id=commit_id),
439 repo_name=backend.repo_name, commit_id=commit_id),
440 params=params)
440 params=params)
441
441
442 comments = ChangesetComment.query().all()
442 comments = ChangesetComment.query().all()
443 assert len(comments) == 1
443 assert len(comments) == 1
444 comment_id = comments[0].comment_id
444 comment_id = comments[0].comment_id
445
445
446 comment = ChangesetComment.get(comment_id)
446 comment = ChangesetComment.get(comment_id)
447 comment.immutable_state = ChangesetComment.OP_IMMUTABLE
447 comment.immutable_state = ChangesetComment.OP_IMMUTABLE
448 Session().add(comment)
448 Session().add(comment)
449 Session().commit()
449 Session().commit()
450
450
451 self.app.post(
451 self.app.post(
452 route_path('repo_commit_comment_delete',
452 route_path('repo_commit_comment_delete',
453 repo_name=backend.repo_name,
453 repo_name=backend.repo_name,
454 commit_id=commit_id,
454 commit_id=commit_id,
455 comment_id=comment_id),
455 comment_id=comment_id),
456 params={'csrf_token': self.csrf_token},
456 params={'csrf_token': self.csrf_token},
457 status=403)
457 status=403)
458
458
459 @pytest.mark.parametrize('renderer, text_input, output', [
459 @pytest.mark.parametrize('renderer, text_input, output', [
460 ('rst', 'plain text', '<p>plain text</p>'),
460 ('rst', 'plain text', '<p>plain text</p>'),
461 ('rst', 'header\n======', '<h1 class="title">header</h1>'),
461 ('rst', 'header\n======', '<h1 class="title">header</h1>'),
462 ('rst', '*italics*', '<em>italics</em>'),
462 ('rst', '*italics*', '<em>italics</em>'),
463 ('rst', '**bold**', '<strong>bold</strong>'),
463 ('rst', '**bold**', '<strong>bold</strong>'),
464 ('markdown', 'plain text', '<p>plain text</p>'),
464 ('markdown', 'plain text', '<p>plain text</p>'),
465 ('markdown', '# header', '<h1>header</h1>'),
465 ('markdown', '# header', '<h1>header</h1>'),
466 ('markdown', '*italics*', '<em>italics</em>'),
466 ('markdown', '*italics*', '<em>italics</em>'),
467 ('markdown', '**bold**', '<strong>bold</strong>'),
467 ('markdown', '**bold**', '<strong>bold</strong>'),
468 ], ids=['rst-plain', 'rst-header', 'rst-italics', 'rst-bold', 'md-plain',
468 ], ids=['rst-plain', 'rst-header', 'rst-italics', 'rst-bold', 'md-plain',
469 'md-header', 'md-italics', 'md-bold', ])
469 'md-header', 'md-italics', 'md-bold', ])
470 def test_preview(self, renderer, text_input, output, backend, xhr_header):
470 def test_preview(self, renderer, text_input, output, backend, xhr_header):
471 self.log_user()
471 self.log_user()
472 params = {
472 params = {
473 'renderer': renderer,
473 'renderer': renderer,
474 'text': text_input,
474 'text': text_input,
475 'csrf_token': self.csrf_token
475 'csrf_token': self.csrf_token
476 }
476 }
477 commit_id = '0' * 16 # fake this for tests
477 commit_id = '0' * 16 # fake this for tests
478 response = self.app.post(
478 response = self.app.post(
479 route_path('repo_commit_comment_preview',
479 route_path('repo_commit_comment_preview',
480 repo_name=backend.repo_name, commit_id=commit_id,),
480 repo_name=backend.repo_name, commit_id=commit_id,),
481 params=params,
481 params=params,
482 extra_environ=xhr_header)
482 extra_environ=xhr_header)
483
483
484 response.mustcontain(output)
484 response.mustcontain(output)
485
485
486
486
487 def assert_comment_links(response, comments, inline_comments):
487 def assert_comment_links(response, comments, inline_comments):
488 if comments == 1:
488 response.mustcontain(
489 comments_text = "%d General" % comments
489 '<span class="display-none" id="general-comments-count">{}</span>'.format(comments))
490 else:
490 response.mustcontain(
491 comments_text = "%d General" % comments
491 '<span class="display-none" id="inline-comments-count">{}</span>'.format(inline_comments))
492
493 if inline_comments == 1:
494 inline_comments_text = "%d Inline" % inline_comments
495 else:
496 inline_comments_text = "%d Inline" % inline_comments
497
492
498 if comments:
499 response.mustcontain('<a href="#comments">%s</a>,' % comments_text)
500 else:
501 response.mustcontain(comments_text)
502
493
503 if inline_comments:
494
504 response.mustcontain(
505 'id="inline-comments-counter">%s' % inline_comments_text)
506 else:
507 response.mustcontain(inline_comments_text)
@@ -1,725 +1,782 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
22 import logging
21 import logging
22 import collections
23
23
24 from pyramid.httpexceptions import (
24 from pyramid.httpexceptions import (
25 HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict)
25 HTTPNotFound, HTTPBadRequest, HTTPFound, HTTPForbidden, HTTPConflict)
26 from pyramid.view import view_config
26 from pyramid.view import view_config
27 from pyramid.renderers import render
27 from pyramid.renderers import render
28 from pyramid.response import Response
28 from pyramid.response import Response
29
29
30 from rhodecode.apps._base import RepoAppView
30 from rhodecode.apps._base import RepoAppView
31 from rhodecode.apps.file_store import utils as store_utils
31 from rhodecode.apps.file_store import utils as store_utils
32 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException
32 from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException
33
33
34 from rhodecode.lib import diffs, codeblocks
34 from rhodecode.lib import diffs, codeblocks
35 from rhodecode.lib.auth import (
35 from rhodecode.lib.auth import (
36 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
36 LoginRequired, HasRepoPermissionAnyDecorator, NotAnonymous, CSRFRequired)
37
37 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.compat import OrderedDict
38 from rhodecode.lib.compat import OrderedDict
39 from rhodecode.lib.diffs import (
39 from rhodecode.lib.diffs import (
40 cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
40 cache_diff, load_cached_diff, diff_cache_exist, get_diff_context,
41 get_diff_whitespace_flag)
41 get_diff_whitespace_flag)
42 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
42 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError, CommentVersionMismatch
43 import rhodecode.lib.helpers as h
43 import rhodecode.lib.helpers as h
44 from rhodecode.lib.utils2 import safe_unicode, str2bool
44 from rhodecode.lib.utils2 import safe_unicode, str2bool, StrictAttributeDict
45 from rhodecode.lib.vcs.backends.base import EmptyCommit
45 from rhodecode.lib.vcs.backends.base import EmptyCommit
46 from rhodecode.lib.vcs.exceptions import (
46 from rhodecode.lib.vcs.exceptions import (
47 RepositoryError, CommitDoesNotExistError)
47 RepositoryError, CommitDoesNotExistError)
48 from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore, \
48 from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore, \
49 ChangesetCommentHistory
49 ChangesetCommentHistory
50 from rhodecode.model.changeset_status import ChangesetStatusModel
50 from rhodecode.model.changeset_status import ChangesetStatusModel
51 from rhodecode.model.comment import CommentsModel
51 from rhodecode.model.comment import CommentsModel
52 from rhodecode.model.meta import Session
52 from rhodecode.model.meta import Session
53 from rhodecode.model.settings import VcsSettingsModel
53 from rhodecode.model.settings import VcsSettingsModel
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 def _update_with_GET(params, request):
58 def _update_with_GET(params, request):
59 for k in ['diff1', 'diff2', 'diff']:
59 for k in ['diff1', 'diff2', 'diff']:
60 params[k] += request.GET.getall(k)
60 params[k] += request.GET.getall(k)
61
61
62
62
63 class RepoCommitsView(RepoAppView):
63 class RepoCommitsView(RepoAppView):
64 def load_default_context(self):
64 def load_default_context(self):
65 c = self._get_local_tmpl_context(include_app_defaults=True)
65 c = self._get_local_tmpl_context(include_app_defaults=True)
66 c.rhodecode_repo = self.rhodecode_vcs_repo
66 c.rhodecode_repo = self.rhodecode_vcs_repo
67
67
68 return c
68 return c
69
69
70 def _is_diff_cache_enabled(self, target_repo):
70 def _is_diff_cache_enabled(self, target_repo):
71 caching_enabled = self._get_general_setting(
71 caching_enabled = self._get_general_setting(
72 target_repo, 'rhodecode_diff_cache')
72 target_repo, 'rhodecode_diff_cache')
73 log.debug('Diff caching enabled: %s', caching_enabled)
73 log.debug('Diff caching enabled: %s', caching_enabled)
74 return caching_enabled
74 return caching_enabled
75
75
76 def _commit(self, commit_id_range, method):
76 def _commit(self, commit_id_range, method):
77 _ = self.request.translate
77 _ = self.request.translate
78 c = self.load_default_context()
78 c = self.load_default_context()
79 c.fulldiff = self.request.GET.get('fulldiff')
79 c.fulldiff = self.request.GET.get('fulldiff')
80
80
81 # fetch global flags of ignore ws or context lines
81 # fetch global flags of ignore ws or context lines
82 diff_context = get_diff_context(self.request)
82 diff_context = get_diff_context(self.request)
83 hide_whitespace_changes = get_diff_whitespace_flag(self.request)
83 hide_whitespace_changes = get_diff_whitespace_flag(self.request)
84
84
85 # diff_limit will cut off the whole diff if the limit is applied
85 # diff_limit will cut off the whole diff if the limit is applied
86 # otherwise it will just hide the big files from the front-end
86 # otherwise it will just hide the big files from the front-end
87 diff_limit = c.visual.cut_off_limit_diff
87 diff_limit = c.visual.cut_off_limit_diff
88 file_limit = c.visual.cut_off_limit_file
88 file_limit = c.visual.cut_off_limit_file
89
89
90
91 # get ranges of commit ids if preset
90 # get ranges of commit ids if preset
92 commit_range = commit_id_range.split('...')[:2]
91 commit_range = commit_id_range.split('...')[:2]
93
92
94 try:
93 try:
95 pre_load = ['affected_files', 'author', 'branch', 'date',
94 pre_load = ['affected_files', 'author', 'branch', 'date',
96 'message', 'parents']
95 'message', 'parents']
97 if self.rhodecode_vcs_repo.alias == 'hg':
96 if self.rhodecode_vcs_repo.alias == 'hg':
98 pre_load += ['hidden', 'obsolete', 'phase']
97 pre_load += ['hidden', 'obsolete', 'phase']
99
98
100 if len(commit_range) == 2:
99 if len(commit_range) == 2:
101 commits = self.rhodecode_vcs_repo.get_commits(
100 commits = self.rhodecode_vcs_repo.get_commits(
102 start_id=commit_range[0], end_id=commit_range[1],
101 start_id=commit_range[0], end_id=commit_range[1],
103 pre_load=pre_load, translate_tags=False)
102 pre_load=pre_load, translate_tags=False)
104 commits = list(commits)
103 commits = list(commits)
105 else:
104 else:
106 commits = [self.rhodecode_vcs_repo.get_commit(
105 commits = [self.rhodecode_vcs_repo.get_commit(
107 commit_id=commit_id_range, pre_load=pre_load)]
106 commit_id=commit_id_range, pre_load=pre_load)]
108
107
109 c.commit_ranges = commits
108 c.commit_ranges = commits
110 if not c.commit_ranges:
109 if not c.commit_ranges:
111 raise RepositoryError('The commit range returned an empty result')
110 raise RepositoryError('The commit range returned an empty result')
112 except CommitDoesNotExistError as e:
111 except CommitDoesNotExistError as e:
113 msg = _('No such commit exists. Org exception: `{}`').format(e)
112 msg = _('No such commit exists. Org exception: `{}`').format(e)
114 h.flash(msg, category='error')
113 h.flash(msg, category='error')
115 raise HTTPNotFound()
114 raise HTTPNotFound()
116 except Exception:
115 except Exception:
117 log.exception("General failure")
116 log.exception("General failure")
118 raise HTTPNotFound()
117 raise HTTPNotFound()
118 single_commit = len(c.commit_ranges) == 1
119
119
120 c.changes = OrderedDict()
120 c.changes = OrderedDict()
121 c.lines_added = 0
121 c.lines_added = 0
122 c.lines_deleted = 0
122 c.lines_deleted = 0
123
123
124 # auto collapse if we have more than limit
124 # auto collapse if we have more than limit
125 collapse_limit = diffs.DiffProcessor._collapse_commits_over
125 collapse_limit = diffs.DiffProcessor._collapse_commits_over
126 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
126 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
127
127
128 c.commit_statuses = ChangesetStatus.STATUSES
128 c.commit_statuses = ChangesetStatus.STATUSES
129 c.inline_comments = []
129 c.inline_comments = []
130 c.files = []
130 c.files = []
131
131
132 c.statuses = []
133 c.comments = []
132 c.comments = []
134 c.unresolved_comments = []
133 c.unresolved_comments = []
135 c.resolved_comments = []
134 c.resolved_comments = []
136 if len(c.commit_ranges) == 1:
135
136 # Single commit
137 if single_commit:
137 commit = c.commit_ranges[0]
138 commit = c.commit_ranges[0]
138 c.comments = CommentsModel().get_comments(
139 c.comments = CommentsModel().get_comments(
139 self.db_repo.repo_id,
140 self.db_repo.repo_id,
140 revision=commit.raw_id)
141 revision=commit.raw_id)
141 c.statuses.append(ChangesetStatusModel().get_status(
142
142 self.db_repo.repo_id, commit.raw_id))
143 # comments from PR
143 # comments from PR
144 statuses = ChangesetStatusModel().get_statuses(
144 statuses = ChangesetStatusModel().get_statuses(
145 self.db_repo.repo_id, commit.raw_id,
145 self.db_repo.repo_id, commit.raw_id,
146 with_revisions=True)
146 with_revisions=True)
147 prs = set(st.pull_request for st in statuses
147
148 if st.pull_request is not None)
148 prs = set()
149 reviewers = list()
150 reviewers_duplicates = set() # to not have duplicates from multiple votes
151 for c_status in statuses:
152
153 # extract associated pull-requests from votes
154 if c_status.pull_request:
155 prs.add(c_status.pull_request)
156
157 # extract reviewers
158 _user_id = c_status.author.user_id
159 if _user_id not in reviewers_duplicates:
160 reviewers.append(
161 StrictAttributeDict({
162 'user': c_status.author,
163
164 # fake attributed for commit, page that we don't have
165 # but we share the display with PR page
166 'mandatory': False,
167 'reasons': [],
168 'rule_user_group_data': lambda: None
169 })
170 )
171 reviewers_duplicates.add(_user_id)
172
173 c.allowed_reviewers = reviewers
149 # from associated statuses, check the pull requests, and
174 # from associated statuses, check the pull requests, and
150 # show comments from them
175 # show comments from them
151 for pr in prs:
176 for pr in prs:
152 c.comments.extend(pr.comments)
177 c.comments.extend(pr.comments)
153
178
154 c.unresolved_comments = CommentsModel()\
179 c.unresolved_comments = CommentsModel()\
155 .get_commit_unresolved_todos(commit.raw_id)
180 .get_commit_unresolved_todos(commit.raw_id)
156 c.resolved_comments = CommentsModel()\
181 c.resolved_comments = CommentsModel()\
157 .get_commit_resolved_todos(commit.raw_id)
182 .get_commit_resolved_todos(commit.raw_id)
158
183
184 c.inline_comments_flat = CommentsModel()\
185 .get_commit_inline_comments(commit.raw_id)
186
187 review_statuses = ChangesetStatusModel().aggregate_votes_by_user(
188 statuses, reviewers)
189
190 c.commit_review_status = ChangesetStatus.STATUS_NOT_REVIEWED
191
192 c.commit_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
193
194 for review_obj, member, reasons, mandatory, status in review_statuses:
195 member_reviewer = h.reviewer_as_json(
196 member, reasons=reasons, mandatory=mandatory,
197 user_group=None
198 )
199
200 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
201 member_reviewer['review_status'] = current_review_status
202 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
203 member_reviewer['allowed_to_update'] = False
204 c.commit_set_reviewers_data_json['reviewers'].append(member_reviewer)
205
206 c.commit_set_reviewers_data_json = json.dumps(c.commit_set_reviewers_data_json)
207
208 # NOTE(marcink): this uses the same voting logic as in pull-requests
209 c.commit_review_status = ChangesetStatusModel().calculate_status(review_statuses)
210 c.commit_broadcast_channel = u'/repo${}$/commit/{}'.format(
211 c.repo_name,
212 commit.raw_id
213 )
214
159 diff = None
215 diff = None
160 # Iterate over ranges (default commit view is always one commit)
216 # Iterate over ranges (default commit view is always one commit)
161 for commit in c.commit_ranges:
217 for commit in c.commit_ranges:
162 c.changes[commit.raw_id] = []
218 c.changes[commit.raw_id] = []
163
219
164 commit2 = commit
220 commit2 = commit
165 commit1 = commit.first_parent
221 commit1 = commit.first_parent
166
222
167 if method == 'show':
223 if method == 'show':
168 inline_comments = CommentsModel().get_inline_comments(
224 inline_comments = CommentsModel().get_inline_comments(
169 self.db_repo.repo_id, revision=commit.raw_id)
225 self.db_repo.repo_id, revision=commit.raw_id)
170 c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
226 c.inline_cnt = len(CommentsModel().get_inline_comments_as_list(
171 inline_comments))
227 inline_comments))
172 c.inline_comments = inline_comments
228 c.inline_comments = inline_comments
173
229
174 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
230 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(
175 self.db_repo)
231 self.db_repo)
176 cache_file_path = diff_cache_exist(
232 cache_file_path = diff_cache_exist(
177 cache_path, 'diff', commit.raw_id,
233 cache_path, 'diff', commit.raw_id,
178 hide_whitespace_changes, diff_context, c.fulldiff)
234 hide_whitespace_changes, diff_context, c.fulldiff)
179
235
180 caching_enabled = self._is_diff_cache_enabled(self.db_repo)
236 caching_enabled = self._is_diff_cache_enabled(self.db_repo)
181 force_recache = str2bool(self.request.GET.get('force_recache'))
237 force_recache = str2bool(self.request.GET.get('force_recache'))
182
238
183 cached_diff = None
239 cached_diff = None
184 if caching_enabled:
240 if caching_enabled:
185 cached_diff = load_cached_diff(cache_file_path)
241 cached_diff = load_cached_diff(cache_file_path)
186
242
187 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
243 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
188 if not force_recache and has_proper_diff_cache:
244 if not force_recache and has_proper_diff_cache:
189 diffset = cached_diff['diff']
245 diffset = cached_diff['diff']
190 else:
246 else:
191 vcs_diff = self.rhodecode_vcs_repo.get_diff(
247 vcs_diff = self.rhodecode_vcs_repo.get_diff(
192 commit1, commit2,
248 commit1, commit2,
193 ignore_whitespace=hide_whitespace_changes,
249 ignore_whitespace=hide_whitespace_changes,
194 context=diff_context)
250 context=diff_context)
195
251
196 diff_processor = diffs.DiffProcessor(
252 diff_processor = diffs.DiffProcessor(
197 vcs_diff, format='newdiff', diff_limit=diff_limit,
253 vcs_diff, format='newdiff', diff_limit=diff_limit,
198 file_limit=file_limit, show_full_diff=c.fulldiff)
254 file_limit=file_limit, show_full_diff=c.fulldiff)
199
255
200 _parsed = diff_processor.prepare()
256 _parsed = diff_processor.prepare()
201
257
202 diffset = codeblocks.DiffSet(
258 diffset = codeblocks.DiffSet(
203 repo_name=self.db_repo_name,
259 repo_name=self.db_repo_name,
204 source_node_getter=codeblocks.diffset_node_getter(commit1),
260 source_node_getter=codeblocks.diffset_node_getter(commit1),
205 target_node_getter=codeblocks.diffset_node_getter(commit2))
261 target_node_getter=codeblocks.diffset_node_getter(commit2))
206
262
207 diffset = self.path_filter.render_patchset_filtered(
263 diffset = self.path_filter.render_patchset_filtered(
208 diffset, _parsed, commit1.raw_id, commit2.raw_id)
264 diffset, _parsed, commit1.raw_id, commit2.raw_id)
209
265
210 # save cached diff
266 # save cached diff
211 if caching_enabled:
267 if caching_enabled:
212 cache_diff(cache_file_path, diffset, None)
268 cache_diff(cache_file_path, diffset, None)
213
269
214 c.limited_diff = diffset.limited_diff
270 c.limited_diff = diffset.limited_diff
215 c.changes[commit.raw_id] = diffset
271 c.changes[commit.raw_id] = diffset
216 else:
272 else:
217 # TODO(marcink): no cache usage here...
273 # TODO(marcink): no cache usage here...
218 _diff = self.rhodecode_vcs_repo.get_diff(
274 _diff = self.rhodecode_vcs_repo.get_diff(
219 commit1, commit2,
275 commit1, commit2,
220 ignore_whitespace=hide_whitespace_changes, context=diff_context)
276 ignore_whitespace=hide_whitespace_changes, context=diff_context)
221 diff_processor = diffs.DiffProcessor(
277 diff_processor = diffs.DiffProcessor(
222 _diff, format='newdiff', diff_limit=diff_limit,
278 _diff, format='newdiff', diff_limit=diff_limit,
223 file_limit=file_limit, show_full_diff=c.fulldiff)
279 file_limit=file_limit, show_full_diff=c.fulldiff)
224 # downloads/raw we only need RAW diff nothing else
280 # downloads/raw we only need RAW diff nothing else
225 diff = self.path_filter.get_raw_patch(diff_processor)
281 diff = self.path_filter.get_raw_patch(diff_processor)
226 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
282 c.changes[commit.raw_id] = [None, None, None, None, diff, None, None]
227
283
228 # sort comments by how they were generated
284 # sort comments by how they were generated
229 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
285 c.comments = sorted(c.comments, key=lambda x: x.comment_id)
230 c.at_version_num = None
286 c.at_version_num = None
231
287
232 if len(c.commit_ranges) == 1:
288 if len(c.commit_ranges) == 1:
233 c.commit = c.commit_ranges[0]
289 c.commit = c.commit_ranges[0]
234 c.parent_tmpl = ''.join(
290 c.parent_tmpl = ''.join(
235 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
291 '# Parent %s\n' % x.raw_id for x in c.commit.parents)
236
292
237 if method == 'download':
293 if method == 'download':
238 response = Response(diff)
294 response = Response(diff)
239 response.content_type = 'text/plain'
295 response.content_type = 'text/plain'
240 response.content_disposition = (
296 response.content_disposition = (
241 'attachment; filename=%s.diff' % commit_id_range[:12])
297 'attachment; filename=%s.diff' % commit_id_range[:12])
242 return response
298 return response
243 elif method == 'patch':
299 elif method == 'patch':
244 c.diff = safe_unicode(diff)
300 c.diff = safe_unicode(diff)
245 patch = render(
301 patch = render(
246 'rhodecode:templates/changeset/patch_changeset.mako',
302 'rhodecode:templates/changeset/patch_changeset.mako',
247 self._get_template_context(c), self.request)
303 self._get_template_context(c), self.request)
248 response = Response(patch)
304 response = Response(patch)
249 response.content_type = 'text/plain'
305 response.content_type = 'text/plain'
250 return response
306 return response
251 elif method == 'raw':
307 elif method == 'raw':
252 response = Response(diff)
308 response = Response(diff)
253 response.content_type = 'text/plain'
309 response.content_type = 'text/plain'
254 return response
310 return response
255 elif method == 'show':
311 elif method == 'show':
256 if len(c.commit_ranges) == 1:
312 if len(c.commit_ranges) == 1:
257 html = render(
313 html = render(
258 'rhodecode:templates/changeset/changeset.mako',
314 'rhodecode:templates/changeset/changeset.mako',
259 self._get_template_context(c), self.request)
315 self._get_template_context(c), self.request)
260 return Response(html)
316 return Response(html)
261 else:
317 else:
262 c.ancestor = None
318 c.ancestor = None
263 c.target_repo = self.db_repo
319 c.target_repo = self.db_repo
264 html = render(
320 html = render(
265 'rhodecode:templates/changeset/changeset_range.mako',
321 'rhodecode:templates/changeset/changeset_range.mako',
266 self._get_template_context(c), self.request)
322 self._get_template_context(c), self.request)
267 return Response(html)
323 return Response(html)
268
324
269 raise HTTPBadRequest()
325 raise HTTPBadRequest()
270
326
271 @LoginRequired()
327 @LoginRequired()
272 @HasRepoPermissionAnyDecorator(
328 @HasRepoPermissionAnyDecorator(
273 'repository.read', 'repository.write', 'repository.admin')
329 'repository.read', 'repository.write', 'repository.admin')
274 @view_config(
330 @view_config(
275 route_name='repo_commit', request_method='GET',
331 route_name='repo_commit', request_method='GET',
276 renderer=None)
332 renderer=None)
277 def repo_commit_show(self):
333 def repo_commit_show(self):
278 commit_id = self.request.matchdict['commit_id']
334 commit_id = self.request.matchdict['commit_id']
279 return self._commit(commit_id, method='show')
335 return self._commit(commit_id, method='show')
280
336
281 @LoginRequired()
337 @LoginRequired()
282 @HasRepoPermissionAnyDecorator(
338 @HasRepoPermissionAnyDecorator(
283 'repository.read', 'repository.write', 'repository.admin')
339 'repository.read', 'repository.write', 'repository.admin')
284 @view_config(
340 @view_config(
285 route_name='repo_commit_raw', request_method='GET',
341 route_name='repo_commit_raw', request_method='GET',
286 renderer=None)
342 renderer=None)
287 @view_config(
343 @view_config(
288 route_name='repo_commit_raw_deprecated', request_method='GET',
344 route_name='repo_commit_raw_deprecated', request_method='GET',
289 renderer=None)
345 renderer=None)
290 def repo_commit_raw(self):
346 def repo_commit_raw(self):
291 commit_id = self.request.matchdict['commit_id']
347 commit_id = self.request.matchdict['commit_id']
292 return self._commit(commit_id, method='raw')
348 return self._commit(commit_id, method='raw')
293
349
294 @LoginRequired()
350 @LoginRequired()
295 @HasRepoPermissionAnyDecorator(
351 @HasRepoPermissionAnyDecorator(
296 'repository.read', 'repository.write', 'repository.admin')
352 'repository.read', 'repository.write', 'repository.admin')
297 @view_config(
353 @view_config(
298 route_name='repo_commit_patch', request_method='GET',
354 route_name='repo_commit_patch', request_method='GET',
299 renderer=None)
355 renderer=None)
300 def repo_commit_patch(self):
356 def repo_commit_patch(self):
301 commit_id = self.request.matchdict['commit_id']
357 commit_id = self.request.matchdict['commit_id']
302 return self._commit(commit_id, method='patch')
358 return self._commit(commit_id, method='patch')
303
359
304 @LoginRequired()
360 @LoginRequired()
305 @HasRepoPermissionAnyDecorator(
361 @HasRepoPermissionAnyDecorator(
306 'repository.read', 'repository.write', 'repository.admin')
362 'repository.read', 'repository.write', 'repository.admin')
307 @view_config(
363 @view_config(
308 route_name='repo_commit_download', request_method='GET',
364 route_name='repo_commit_download', request_method='GET',
309 renderer=None)
365 renderer=None)
310 def repo_commit_download(self):
366 def repo_commit_download(self):
311 commit_id = self.request.matchdict['commit_id']
367 commit_id = self.request.matchdict['commit_id']
312 return self._commit(commit_id, method='download')
368 return self._commit(commit_id, method='download')
313
369
314 @LoginRequired()
370 @LoginRequired()
315 @NotAnonymous()
371 @NotAnonymous()
316 @HasRepoPermissionAnyDecorator(
372 @HasRepoPermissionAnyDecorator(
317 'repository.read', 'repository.write', 'repository.admin')
373 'repository.read', 'repository.write', 'repository.admin')
318 @CSRFRequired()
374 @CSRFRequired()
319 @view_config(
375 @view_config(
320 route_name='repo_commit_comment_create', request_method='POST',
376 route_name='repo_commit_comment_create', request_method='POST',
321 renderer='json_ext')
377 renderer='json_ext')
322 def repo_commit_comment_create(self):
378 def repo_commit_comment_create(self):
323 _ = self.request.translate
379 _ = self.request.translate
324 commit_id = self.request.matchdict['commit_id']
380 commit_id = self.request.matchdict['commit_id']
325
381
326 c = self.load_default_context()
382 c = self.load_default_context()
327 status = self.request.POST.get('changeset_status', None)
383 status = self.request.POST.get('changeset_status', None)
328 text = self.request.POST.get('text')
384 text = self.request.POST.get('text')
329 comment_type = self.request.POST.get('comment_type')
385 comment_type = self.request.POST.get('comment_type')
330 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
386 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
331
387
332 if status:
388 if status:
333 text = text or (_('Status change %(transition_icon)s %(status)s')
389 text = text or (_('Status change %(transition_icon)s %(status)s')
334 % {'transition_icon': '>',
390 % {'transition_icon': '>',
335 'status': ChangesetStatus.get_status_lbl(status)})
391 'status': ChangesetStatus.get_status_lbl(status)})
336
392
337 multi_commit_ids = []
393 multi_commit_ids = []
338 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
394 for _commit_id in self.request.POST.get('commit_ids', '').split(','):
339 if _commit_id not in ['', None, EmptyCommit.raw_id]:
395 if _commit_id not in ['', None, EmptyCommit.raw_id]:
340 if _commit_id not in multi_commit_ids:
396 if _commit_id not in multi_commit_ids:
341 multi_commit_ids.append(_commit_id)
397 multi_commit_ids.append(_commit_id)
342
398
343 commit_ids = multi_commit_ids or [commit_id]
399 commit_ids = multi_commit_ids or [commit_id]
344
400
345 comment = None
401 comment = None
346 for current_id in filter(None, commit_ids):
402 for current_id in filter(None, commit_ids):
347 comment = CommentsModel().create(
403 comment = CommentsModel().create(
348 text=text,
404 text=text,
349 repo=self.db_repo.repo_id,
405 repo=self.db_repo.repo_id,
350 user=self._rhodecode_db_user.user_id,
406 user=self._rhodecode_db_user.user_id,
351 commit_id=current_id,
407 commit_id=current_id,
352 f_path=self.request.POST.get('f_path'),
408 f_path=self.request.POST.get('f_path'),
353 line_no=self.request.POST.get('line'),
409 line_no=self.request.POST.get('line'),
354 status_change=(ChangesetStatus.get_status_lbl(status)
410 status_change=(ChangesetStatus.get_status_lbl(status)
355 if status else None),
411 if status else None),
356 status_change_type=status,
412 status_change_type=status,
357 comment_type=comment_type,
413 comment_type=comment_type,
358 resolves_comment_id=resolves_comment_id,
414 resolves_comment_id=resolves_comment_id,
359 auth_user=self._rhodecode_user
415 auth_user=self._rhodecode_user
360 )
416 )
361
417
362 # get status if set !
418 # get status if set !
363 if status:
419 if status:
364 # if latest status was from pull request and it's closed
420 # if latest status was from pull request and it's closed
365 # disallow changing status !
421 # disallow changing status !
366 # dont_allow_on_closed_pull_request = True !
422 # dont_allow_on_closed_pull_request = True !
367
423
368 try:
424 try:
369 ChangesetStatusModel().set_status(
425 ChangesetStatusModel().set_status(
370 self.db_repo.repo_id,
426 self.db_repo.repo_id,
371 status,
427 status,
372 self._rhodecode_db_user.user_id,
428 self._rhodecode_db_user.user_id,
373 comment,
429 comment,
374 revision=current_id,
430 revision=current_id,
375 dont_allow_on_closed_pull_request=True
431 dont_allow_on_closed_pull_request=True
376 )
432 )
377 except StatusChangeOnClosedPullRequestError:
433 except StatusChangeOnClosedPullRequestError:
378 msg = _('Changing the status of a commit associated with '
434 msg = _('Changing the status of a commit associated with '
379 'a closed pull request is not allowed')
435 'a closed pull request is not allowed')
380 log.exception(msg)
436 log.exception(msg)
381 h.flash(msg, category='warning')
437 h.flash(msg, category='warning')
382 raise HTTPFound(h.route_path(
438 raise HTTPFound(h.route_path(
383 'repo_commit', repo_name=self.db_repo_name,
439 'repo_commit', repo_name=self.db_repo_name,
384 commit_id=current_id))
440 commit_id=current_id))
385
441
386 commit = self.db_repo.get_commit(current_id)
442 commit = self.db_repo.get_commit(current_id)
387 CommentsModel().trigger_commit_comment_hook(
443 CommentsModel().trigger_commit_comment_hook(
388 self.db_repo, self._rhodecode_user, 'create',
444 self.db_repo, self._rhodecode_user, 'create',
389 data={'comment': comment, 'commit': commit})
445 data={'comment': comment, 'commit': commit})
390
446
391 # finalize, commit and redirect
447 # finalize, commit and redirect
392 Session().commit()
448 Session().commit()
393
449
394 data = {
450 data = {
395 'target_id': h.safeid(h.safe_unicode(
451 'target_id': h.safeid(h.safe_unicode(
396 self.request.POST.get('f_path'))),
452 self.request.POST.get('f_path'))),
397 }
453 }
398 if comment:
454 if comment:
399 c.co = comment
455 c.co = comment
456 c.at_version_num = 0
400 rendered_comment = render(
457 rendered_comment = render(
401 'rhodecode:templates/changeset/changeset_comment_block.mako',
458 'rhodecode:templates/changeset/changeset_comment_block.mako',
402 self._get_template_context(c), self.request)
459 self._get_template_context(c), self.request)
403
460
404 data.update(comment.get_dict())
461 data.update(comment.get_dict())
405 data.update({'rendered_text': rendered_comment})
462 data.update({'rendered_text': rendered_comment})
406
463
407 return data
464 return data
408
465
409 @LoginRequired()
466 @LoginRequired()
410 @NotAnonymous()
467 @NotAnonymous()
411 @HasRepoPermissionAnyDecorator(
468 @HasRepoPermissionAnyDecorator(
412 'repository.read', 'repository.write', 'repository.admin')
469 'repository.read', 'repository.write', 'repository.admin')
413 @CSRFRequired()
470 @CSRFRequired()
414 @view_config(
471 @view_config(
415 route_name='repo_commit_comment_preview', request_method='POST',
472 route_name='repo_commit_comment_preview', request_method='POST',
416 renderer='string', xhr=True)
473 renderer='string', xhr=True)
417 def repo_commit_comment_preview(self):
474 def repo_commit_comment_preview(self):
418 # Technically a CSRF token is not needed as no state changes with this
475 # Technically a CSRF token is not needed as no state changes with this
419 # call. However, as this is a POST is better to have it, so automated
476 # call. However, as this is a POST is better to have it, so automated
420 # tools don't flag it as potential CSRF.
477 # tools don't flag it as potential CSRF.
421 # Post is required because the payload could be bigger than the maximum
478 # Post is required because the payload could be bigger than the maximum
422 # allowed by GET.
479 # allowed by GET.
423
480
424 text = self.request.POST.get('text')
481 text = self.request.POST.get('text')
425 renderer = self.request.POST.get('renderer') or 'rst'
482 renderer = self.request.POST.get('renderer') or 'rst'
426 if text:
483 if text:
427 return h.render(text, renderer=renderer, mentions=True,
484 return h.render(text, renderer=renderer, mentions=True,
428 repo_name=self.db_repo_name)
485 repo_name=self.db_repo_name)
429 return ''
486 return ''
430
487
431 @LoginRequired()
488 @LoginRequired()
432 @NotAnonymous()
489 @NotAnonymous()
433 @HasRepoPermissionAnyDecorator(
490 @HasRepoPermissionAnyDecorator(
434 'repository.read', 'repository.write', 'repository.admin')
491 'repository.read', 'repository.write', 'repository.admin')
435 @CSRFRequired()
492 @CSRFRequired()
436 @view_config(
493 @view_config(
437 route_name='repo_commit_comment_history_view', request_method='POST',
494 route_name='repo_commit_comment_history_view', request_method='POST',
438 renderer='string', xhr=True)
495 renderer='string', xhr=True)
439 def repo_commit_comment_history_view(self):
496 def repo_commit_comment_history_view(self):
440 c = self.load_default_context()
497 c = self.load_default_context()
441
498
442 comment_history_id = self.request.matchdict['comment_history_id']
499 comment_history_id = self.request.matchdict['comment_history_id']
443 comment_history = ChangesetCommentHistory.get_or_404(comment_history_id)
500 comment_history = ChangesetCommentHistory.get_or_404(comment_history_id)
444 is_repo_comment = comment_history.comment.repo.repo_id == self.db_repo.repo_id
501 is_repo_comment = comment_history.comment.repo.repo_id == self.db_repo.repo_id
445
502
446 if is_repo_comment:
503 if is_repo_comment:
447 c.comment_history = comment_history
504 c.comment_history = comment_history
448
505
449 rendered_comment = render(
506 rendered_comment = render(
450 'rhodecode:templates/changeset/comment_history.mako',
507 'rhodecode:templates/changeset/comment_history.mako',
451 self._get_template_context(c)
508 self._get_template_context(c)
452 , self.request)
509 , self.request)
453 return rendered_comment
510 return rendered_comment
454 else:
511 else:
455 log.warning('No permissions for user %s to show comment_history_id: %s',
512 log.warning('No permissions for user %s to show comment_history_id: %s',
456 self._rhodecode_db_user, comment_history_id)
513 self._rhodecode_db_user, comment_history_id)
457 raise HTTPNotFound()
514 raise HTTPNotFound()
458
515
459 @LoginRequired()
516 @LoginRequired()
460 @NotAnonymous()
517 @NotAnonymous()
461 @HasRepoPermissionAnyDecorator(
518 @HasRepoPermissionAnyDecorator(
462 'repository.read', 'repository.write', 'repository.admin')
519 'repository.read', 'repository.write', 'repository.admin')
463 @CSRFRequired()
520 @CSRFRequired()
464 @view_config(
521 @view_config(
465 route_name='repo_commit_comment_attachment_upload', request_method='POST',
522 route_name='repo_commit_comment_attachment_upload', request_method='POST',
466 renderer='json_ext', xhr=True)
523 renderer='json_ext', xhr=True)
467 def repo_commit_comment_attachment_upload(self):
524 def repo_commit_comment_attachment_upload(self):
468 c = self.load_default_context()
525 c = self.load_default_context()
469 upload_key = 'attachment'
526 upload_key = 'attachment'
470
527
471 file_obj = self.request.POST.get(upload_key)
528 file_obj = self.request.POST.get(upload_key)
472
529
473 if file_obj is None:
530 if file_obj is None:
474 self.request.response.status = 400
531 self.request.response.status = 400
475 return {'store_fid': None,
532 return {'store_fid': None,
476 'access_path': None,
533 'access_path': None,
477 'error': '{} data field is missing'.format(upload_key)}
534 'error': '{} data field is missing'.format(upload_key)}
478
535
479 if not hasattr(file_obj, 'filename'):
536 if not hasattr(file_obj, 'filename'):
480 self.request.response.status = 400
537 self.request.response.status = 400
481 return {'store_fid': None,
538 return {'store_fid': None,
482 'access_path': None,
539 'access_path': None,
483 'error': 'filename cannot be read from the data field'}
540 'error': 'filename cannot be read from the data field'}
484
541
485 filename = file_obj.filename
542 filename = file_obj.filename
486 file_display_name = filename
543 file_display_name = filename
487
544
488 metadata = {
545 metadata = {
489 'user_uploaded': {'username': self._rhodecode_user.username,
546 'user_uploaded': {'username': self._rhodecode_user.username,
490 'user_id': self._rhodecode_user.user_id,
547 'user_id': self._rhodecode_user.user_id,
491 'ip': self._rhodecode_user.ip_addr}}
548 'ip': self._rhodecode_user.ip_addr}}
492
549
493 # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
550 # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size
494 allowed_extensions = [
551 allowed_extensions = [
495 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
552 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf',
496 '.pptx', '.txt', '.xlsx', '.zip']
553 '.pptx', '.txt', '.xlsx', '.zip']
497 max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
554 max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js
498
555
499 try:
556 try:
500 storage = store_utils.get_file_storage(self.request.registry.settings)
557 storage = store_utils.get_file_storage(self.request.registry.settings)
501 store_uid, metadata = storage.save_file(
558 store_uid, metadata = storage.save_file(
502 file_obj.file, filename, extra_metadata=metadata,
559 file_obj.file, filename, extra_metadata=metadata,
503 extensions=allowed_extensions, max_filesize=max_file_size)
560 extensions=allowed_extensions, max_filesize=max_file_size)
504 except FileNotAllowedException:
561 except FileNotAllowedException:
505 self.request.response.status = 400
562 self.request.response.status = 400
506 permitted_extensions = ', '.join(allowed_extensions)
563 permitted_extensions = ', '.join(allowed_extensions)
507 error_msg = 'File `{}` is not allowed. ' \
564 error_msg = 'File `{}` is not allowed. ' \
508 'Only following extensions are permitted: {}'.format(
565 'Only following extensions are permitted: {}'.format(
509 filename, permitted_extensions)
566 filename, permitted_extensions)
510 return {'store_fid': None,
567 return {'store_fid': None,
511 'access_path': None,
568 'access_path': None,
512 'error': error_msg}
569 'error': error_msg}
513 except FileOverSizeException:
570 except FileOverSizeException:
514 self.request.response.status = 400
571 self.request.response.status = 400
515 limit_mb = h.format_byte_size_binary(max_file_size)
572 limit_mb = h.format_byte_size_binary(max_file_size)
516 return {'store_fid': None,
573 return {'store_fid': None,
517 'access_path': None,
574 'access_path': None,
518 'error': 'File {} is exceeding allowed limit of {}.'.format(
575 'error': 'File {} is exceeding allowed limit of {}.'.format(
519 filename, limit_mb)}
576 filename, limit_mb)}
520
577
521 try:
578 try:
522 entry = FileStore.create(
579 entry = FileStore.create(
523 file_uid=store_uid, filename=metadata["filename"],
580 file_uid=store_uid, filename=metadata["filename"],
524 file_hash=metadata["sha256"], file_size=metadata["size"],
581 file_hash=metadata["sha256"], file_size=metadata["size"],
525 file_display_name=file_display_name,
582 file_display_name=file_display_name,
526 file_description=u'comment attachment `{}`'.format(safe_unicode(filename)),
583 file_description=u'comment attachment `{}`'.format(safe_unicode(filename)),
527 hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
584 hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id,
528 scope_repo_id=self.db_repo.repo_id
585 scope_repo_id=self.db_repo.repo_id
529 )
586 )
530 Session().add(entry)
587 Session().add(entry)
531 Session().commit()
588 Session().commit()
532 log.debug('Stored upload in DB as %s', entry)
589 log.debug('Stored upload in DB as %s', entry)
533 except Exception:
590 except Exception:
534 log.exception('Failed to store file %s', filename)
591 log.exception('Failed to store file %s', filename)
535 self.request.response.status = 400
592 self.request.response.status = 400
536 return {'store_fid': None,
593 return {'store_fid': None,
537 'access_path': None,
594 'access_path': None,
538 'error': 'File {} failed to store in DB.'.format(filename)}
595 'error': 'File {} failed to store in DB.'.format(filename)}
539
596
540 Session().commit()
597 Session().commit()
541
598
542 return {
599 return {
543 'store_fid': store_uid,
600 'store_fid': store_uid,
544 'access_path': h.route_path(
601 'access_path': h.route_path(
545 'download_file', fid=store_uid),
602 'download_file', fid=store_uid),
546 'fqn_access_path': h.route_url(
603 'fqn_access_path': h.route_url(
547 'download_file', fid=store_uid),
604 'download_file', fid=store_uid),
548 'repo_access_path': h.route_path(
605 'repo_access_path': h.route_path(
549 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
606 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
550 'repo_fqn_access_path': h.route_url(
607 'repo_fqn_access_path': h.route_url(
551 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
608 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid),
552 }
609 }
553
610
554 @LoginRequired()
611 @LoginRequired()
555 @NotAnonymous()
612 @NotAnonymous()
556 @HasRepoPermissionAnyDecorator(
613 @HasRepoPermissionAnyDecorator(
557 'repository.read', 'repository.write', 'repository.admin')
614 'repository.read', 'repository.write', 'repository.admin')
558 @CSRFRequired()
615 @CSRFRequired()
559 @view_config(
616 @view_config(
560 route_name='repo_commit_comment_delete', request_method='POST',
617 route_name='repo_commit_comment_delete', request_method='POST',
561 renderer='json_ext')
618 renderer='json_ext')
562 def repo_commit_comment_delete(self):
619 def repo_commit_comment_delete(self):
563 commit_id = self.request.matchdict['commit_id']
620 commit_id = self.request.matchdict['commit_id']
564 comment_id = self.request.matchdict['comment_id']
621 comment_id = self.request.matchdict['comment_id']
565
622
566 comment = ChangesetComment.get_or_404(comment_id)
623 comment = ChangesetComment.get_or_404(comment_id)
567 if not comment:
624 if not comment:
568 log.debug('Comment with id:%s not found, skipping', comment_id)
625 log.debug('Comment with id:%s not found, skipping', comment_id)
569 # comment already deleted in another call probably
626 # comment already deleted in another call probably
570 return True
627 return True
571
628
572 if comment.immutable:
629 if comment.immutable:
573 # don't allow deleting comments that are immutable
630 # don't allow deleting comments that are immutable
574 raise HTTPForbidden()
631 raise HTTPForbidden()
575
632
576 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
633 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
577 super_admin = h.HasPermissionAny('hg.admin')()
634 super_admin = h.HasPermissionAny('hg.admin')()
578 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
635 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
579 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
636 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
580 comment_repo_admin = is_repo_admin and is_repo_comment
637 comment_repo_admin = is_repo_admin and is_repo_comment
581
638
582 if super_admin or comment_owner or comment_repo_admin:
639 if super_admin or comment_owner or comment_repo_admin:
583 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
640 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
584 Session().commit()
641 Session().commit()
585 return True
642 return True
586 else:
643 else:
587 log.warning('No permissions for user %s to delete comment_id: %s',
644 log.warning('No permissions for user %s to delete comment_id: %s',
588 self._rhodecode_db_user, comment_id)
645 self._rhodecode_db_user, comment_id)
589 raise HTTPNotFound()
646 raise HTTPNotFound()
590
647
591 @LoginRequired()
648 @LoginRequired()
592 @NotAnonymous()
649 @NotAnonymous()
593 @HasRepoPermissionAnyDecorator(
650 @HasRepoPermissionAnyDecorator(
594 'repository.read', 'repository.write', 'repository.admin')
651 'repository.read', 'repository.write', 'repository.admin')
595 @CSRFRequired()
652 @CSRFRequired()
596 @view_config(
653 @view_config(
597 route_name='repo_commit_comment_edit', request_method='POST',
654 route_name='repo_commit_comment_edit', request_method='POST',
598 renderer='json_ext')
655 renderer='json_ext')
599 def repo_commit_comment_edit(self):
656 def repo_commit_comment_edit(self):
600 self.load_default_context()
657 self.load_default_context()
601
658
602 comment_id = self.request.matchdict['comment_id']
659 comment_id = self.request.matchdict['comment_id']
603 comment = ChangesetComment.get_or_404(comment_id)
660 comment = ChangesetComment.get_or_404(comment_id)
604
661
605 if comment.immutable:
662 if comment.immutable:
606 # don't allow deleting comments that are immutable
663 # don't allow deleting comments that are immutable
607 raise HTTPForbidden()
664 raise HTTPForbidden()
608
665
609 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
666 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
610 super_admin = h.HasPermissionAny('hg.admin')()
667 super_admin = h.HasPermissionAny('hg.admin')()
611 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
668 comment_owner = (comment.author.user_id == self._rhodecode_db_user.user_id)
612 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
669 is_repo_comment = comment.repo.repo_id == self.db_repo.repo_id
613 comment_repo_admin = is_repo_admin and is_repo_comment
670 comment_repo_admin = is_repo_admin and is_repo_comment
614
671
615 if super_admin or comment_owner or comment_repo_admin:
672 if super_admin or comment_owner or comment_repo_admin:
616 text = self.request.POST.get('text')
673 text = self.request.POST.get('text')
617 version = self.request.POST.get('version')
674 version = self.request.POST.get('version')
618 if text == comment.text:
675 if text == comment.text:
619 log.warning(
676 log.warning(
620 'Comment(repo): '
677 'Comment(repo): '
621 'Trying to create new version '
678 'Trying to create new version '
622 'with the same comment body {}'.format(
679 'with the same comment body {}'.format(
623 comment_id,
680 comment_id,
624 )
681 )
625 )
682 )
626 raise HTTPNotFound()
683 raise HTTPNotFound()
627
684
628 if version.isdigit():
685 if version.isdigit():
629 version = int(version)
686 version = int(version)
630 else:
687 else:
631 log.warning(
688 log.warning(
632 'Comment(repo): Wrong version type {} {} '
689 'Comment(repo): Wrong version type {} {} '
633 'for comment {}'.format(
690 'for comment {}'.format(
634 version,
691 version,
635 type(version),
692 type(version),
636 comment_id,
693 comment_id,
637 )
694 )
638 )
695 )
639 raise HTTPNotFound()
696 raise HTTPNotFound()
640
697
641 try:
698 try:
642 comment_history = CommentsModel().edit(
699 comment_history = CommentsModel().edit(
643 comment_id=comment_id,
700 comment_id=comment_id,
644 text=text,
701 text=text,
645 auth_user=self._rhodecode_user,
702 auth_user=self._rhodecode_user,
646 version=version,
703 version=version,
647 )
704 )
648 except CommentVersionMismatch:
705 except CommentVersionMismatch:
649 raise HTTPConflict()
706 raise HTTPConflict()
650
707
651 if not comment_history:
708 if not comment_history:
652 raise HTTPNotFound()
709 raise HTTPNotFound()
653
710
654 commit_id = self.request.matchdict['commit_id']
711 commit_id = self.request.matchdict['commit_id']
655 commit = self.db_repo.get_commit(commit_id)
712 commit = self.db_repo.get_commit(commit_id)
656 CommentsModel().trigger_commit_comment_hook(
713 CommentsModel().trigger_commit_comment_hook(
657 self.db_repo, self._rhodecode_user, 'edit',
714 self.db_repo, self._rhodecode_user, 'edit',
658 data={'comment': comment, 'commit': commit})
715 data={'comment': comment, 'commit': commit})
659
716
660 Session().commit()
717 Session().commit()
661 return {
718 return {
662 'comment_history_id': comment_history.comment_history_id,
719 'comment_history_id': comment_history.comment_history_id,
663 'comment_id': comment.comment_id,
720 'comment_id': comment.comment_id,
664 'comment_version': comment_history.version,
721 'comment_version': comment_history.version,
665 'comment_author_username': comment_history.author.username,
722 'comment_author_username': comment_history.author.username,
666 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
723 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
667 'comment_created_on': h.age_component(comment_history.created_on,
724 'comment_created_on': h.age_component(comment_history.created_on,
668 time_is_local=True),
725 time_is_local=True),
669 }
726 }
670 else:
727 else:
671 log.warning('No permissions for user %s to edit comment_id: %s',
728 log.warning('No permissions for user %s to edit comment_id: %s',
672 self._rhodecode_db_user, comment_id)
729 self._rhodecode_db_user, comment_id)
673 raise HTTPNotFound()
730 raise HTTPNotFound()
674
731
675 @LoginRequired()
732 @LoginRequired()
676 @HasRepoPermissionAnyDecorator(
733 @HasRepoPermissionAnyDecorator(
677 'repository.read', 'repository.write', 'repository.admin')
734 'repository.read', 'repository.write', 'repository.admin')
678 @view_config(
735 @view_config(
679 route_name='repo_commit_data', request_method='GET',
736 route_name='repo_commit_data', request_method='GET',
680 renderer='json_ext', xhr=True)
737 renderer='json_ext', xhr=True)
681 def repo_commit_data(self):
738 def repo_commit_data(self):
682 commit_id = self.request.matchdict['commit_id']
739 commit_id = self.request.matchdict['commit_id']
683 self.load_default_context()
740 self.load_default_context()
684
741
685 try:
742 try:
686 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
743 return self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
687 except CommitDoesNotExistError as e:
744 except CommitDoesNotExistError as e:
688 return EmptyCommit(message=str(e))
745 return EmptyCommit(message=str(e))
689
746
690 @LoginRequired()
747 @LoginRequired()
691 @HasRepoPermissionAnyDecorator(
748 @HasRepoPermissionAnyDecorator(
692 'repository.read', 'repository.write', 'repository.admin')
749 'repository.read', 'repository.write', 'repository.admin')
693 @view_config(
750 @view_config(
694 route_name='repo_commit_children', request_method='GET',
751 route_name='repo_commit_children', request_method='GET',
695 renderer='json_ext', xhr=True)
752 renderer='json_ext', xhr=True)
696 def repo_commit_children(self):
753 def repo_commit_children(self):
697 commit_id = self.request.matchdict['commit_id']
754 commit_id = self.request.matchdict['commit_id']
698 self.load_default_context()
755 self.load_default_context()
699
756
700 try:
757 try:
701 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
758 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
702 children = commit.children
759 children = commit.children
703 except CommitDoesNotExistError:
760 except CommitDoesNotExistError:
704 children = []
761 children = []
705
762
706 result = {"results": children}
763 result = {"results": children}
707 return result
764 return result
708
765
709 @LoginRequired()
766 @LoginRequired()
710 @HasRepoPermissionAnyDecorator(
767 @HasRepoPermissionAnyDecorator(
711 'repository.read', 'repository.write', 'repository.admin')
768 'repository.read', 'repository.write', 'repository.admin')
712 @view_config(
769 @view_config(
713 route_name='repo_commit_parents', request_method='GET',
770 route_name='repo_commit_parents', request_method='GET',
714 renderer='json_ext')
771 renderer='json_ext')
715 def repo_commit_parents(self):
772 def repo_commit_parents(self):
716 commit_id = self.request.matchdict['commit_id']
773 commit_id = self.request.matchdict['commit_id']
717 self.load_default_context()
774 self.load_default_context()
718
775
719 try:
776 try:
720 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
777 commit = self.rhodecode_vcs_repo.get_commit(commit_id=commit_id)
721 parents = commit.parents
778 parents = commit.parents
722 except CommitDoesNotExistError:
779 except CommitDoesNotExistError:
723 parents = []
780 parents = []
724 result = {"results": parents}
781 result = {"results": parents}
725 return result
782 return result
@@ -1,1753 +1,1757 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import collections
22 import collections
23
23
24 import formencode
24 import formencode
25 import formencode.htmlfill
25 import formencode.htmlfill
26 import peppercorn
26 import peppercorn
27 from pyramid.httpexceptions import (
27 from pyramid.httpexceptions import (
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest, HTTPConflict)
28 HTTPFound, HTTPNotFound, HTTPForbidden, HTTPBadRequest, HTTPConflict)
29 from pyramid.view import view_config
29 from pyramid.view import view_config
30 from pyramid.renderers import render
30 from pyramid.renderers import render
31
31
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
32 from rhodecode.apps._base import RepoAppView, DataGridAppView
33
33
34 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
34 from rhodecode.lib import helpers as h, diffs, codeblocks, channelstream
35 from rhodecode.lib.base import vcs_operation_context
35 from rhodecode.lib.base import vcs_operation_context
36 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
36 from rhodecode.lib.diffs import load_cached_diff, cache_diff, diff_cache_exist
37 from rhodecode.lib.exceptions import CommentVersionMismatch
37 from rhodecode.lib.exceptions import CommentVersionMismatch
38 from rhodecode.lib.ext_json import json
38 from rhodecode.lib.ext_json import json
39 from rhodecode.lib.auth import (
39 from rhodecode.lib.auth import (
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
40 LoginRequired, HasRepoPermissionAny, HasRepoPermissionAnyDecorator,
41 NotAnonymous, CSRFRequired)
41 NotAnonymous, CSRFRequired)
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode
42 from rhodecode.lib.utils2 import str2bool, safe_str, safe_unicode, safe_int
43 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
43 from rhodecode.lib.vcs.backends.base import EmptyCommit, UpdateFailureReason
44 from rhodecode.lib.vcs.exceptions import (
44 from rhodecode.lib.vcs.exceptions import (
45 CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
45 CommitDoesNotExistError, RepositoryRequirementError, EmptyRepositoryError)
46 from rhodecode.model.changeset_status import ChangesetStatusModel
46 from rhodecode.model.changeset_status import ChangesetStatusModel
47 from rhodecode.model.comment import CommentsModel
47 from rhodecode.model.comment import CommentsModel
48 from rhodecode.model.db import (
48 from rhodecode.model.db import (
49 func, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository)
49 func, or_, PullRequest, ChangesetComment, ChangesetStatus, Repository)
50 from rhodecode.model.forms import PullRequestForm
50 from rhodecode.model.forms import PullRequestForm
51 from rhodecode.model.meta import Session
51 from rhodecode.model.meta import Session
52 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
52 from rhodecode.model.pull_request import PullRequestModel, MergeCheck
53 from rhodecode.model.scm import ScmModel
53 from rhodecode.model.scm import ScmModel
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class RepoPullRequestsView(RepoAppView, DataGridAppView):
58 class RepoPullRequestsView(RepoAppView, DataGridAppView):
59
59
60 def load_default_context(self):
60 def load_default_context(self):
61 c = self._get_local_tmpl_context(include_app_defaults=True)
61 c = self._get_local_tmpl_context(include_app_defaults=True)
62 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
62 c.REVIEW_STATUS_APPROVED = ChangesetStatus.STATUS_APPROVED
63 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
63 c.REVIEW_STATUS_REJECTED = ChangesetStatus.STATUS_REJECTED
64 # backward compat., we use for OLD PRs a plain renderer
64 # backward compat., we use for OLD PRs a plain renderer
65 c.renderer = 'plain'
65 c.renderer = 'plain'
66 return c
66 return c
67
67
68 def _get_pull_requests_list(
68 def _get_pull_requests_list(
69 self, repo_name, source, filter_type, opened_by, statuses):
69 self, repo_name, source, filter_type, opened_by, statuses):
70
70
71 draw, start, limit = self._extract_chunk(self.request)
71 draw, start, limit = self._extract_chunk(self.request)
72 search_q, order_by, order_dir = self._extract_ordering(self.request)
72 search_q, order_by, order_dir = self._extract_ordering(self.request)
73 _render = self.request.get_partial_renderer(
73 _render = self.request.get_partial_renderer(
74 'rhodecode:templates/data_table/_dt_elements.mako')
74 'rhodecode:templates/data_table/_dt_elements.mako')
75
75
76 # pagination
76 # pagination
77
77
78 if filter_type == 'awaiting_review':
78 if filter_type == 'awaiting_review':
79 pull_requests = PullRequestModel().get_awaiting_review(
79 pull_requests = PullRequestModel().get_awaiting_review(
80 repo_name, search_q=search_q, source=source, opened_by=opened_by,
80 repo_name, search_q=search_q, source=source, opened_by=opened_by,
81 statuses=statuses, offset=start, length=limit,
81 statuses=statuses, offset=start, length=limit,
82 order_by=order_by, order_dir=order_dir)
82 order_by=order_by, order_dir=order_dir)
83 pull_requests_total_count = PullRequestModel().count_awaiting_review(
83 pull_requests_total_count = PullRequestModel().count_awaiting_review(
84 repo_name, search_q=search_q, source=source, statuses=statuses,
84 repo_name, search_q=search_q, source=source, statuses=statuses,
85 opened_by=opened_by)
85 opened_by=opened_by)
86 elif filter_type == 'awaiting_my_review':
86 elif filter_type == 'awaiting_my_review':
87 pull_requests = PullRequestModel().get_awaiting_my_review(
87 pull_requests = PullRequestModel().get_awaiting_my_review(
88 repo_name, search_q=search_q, source=source, opened_by=opened_by,
88 repo_name, search_q=search_q, source=source, opened_by=opened_by,
89 user_id=self._rhodecode_user.user_id, statuses=statuses,
89 user_id=self._rhodecode_user.user_id, statuses=statuses,
90 offset=start, length=limit, order_by=order_by,
90 offset=start, length=limit, order_by=order_by,
91 order_dir=order_dir)
91 order_dir=order_dir)
92 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
92 pull_requests_total_count = PullRequestModel().count_awaiting_my_review(
93 repo_name, search_q=search_q, source=source, user_id=self._rhodecode_user.user_id,
93 repo_name, search_q=search_q, source=source, user_id=self._rhodecode_user.user_id,
94 statuses=statuses, opened_by=opened_by)
94 statuses=statuses, opened_by=opened_by)
95 else:
95 else:
96 pull_requests = PullRequestModel().get_all(
96 pull_requests = PullRequestModel().get_all(
97 repo_name, search_q=search_q, source=source, opened_by=opened_by,
97 repo_name, search_q=search_q, source=source, opened_by=opened_by,
98 statuses=statuses, offset=start, length=limit,
98 statuses=statuses, offset=start, length=limit,
99 order_by=order_by, order_dir=order_dir)
99 order_by=order_by, order_dir=order_dir)
100 pull_requests_total_count = PullRequestModel().count_all(
100 pull_requests_total_count = PullRequestModel().count_all(
101 repo_name, search_q=search_q, source=source, statuses=statuses,
101 repo_name, search_q=search_q, source=source, statuses=statuses,
102 opened_by=opened_by)
102 opened_by=opened_by)
103
103
104 data = []
104 data = []
105 comments_model = CommentsModel()
105 comments_model = CommentsModel()
106 for pr in pull_requests:
106 for pr in pull_requests:
107 comments = comments_model.get_all_comments(
107 comments = comments_model.get_all_comments(
108 self.db_repo.repo_id, pull_request=pr)
108 self.db_repo.repo_id, pull_request=pr)
109
109
110 data.append({
110 data.append({
111 'name': _render('pullrequest_name',
111 'name': _render('pullrequest_name',
112 pr.pull_request_id, pr.pull_request_state,
112 pr.pull_request_id, pr.pull_request_state,
113 pr.work_in_progress, pr.target_repo.repo_name),
113 pr.work_in_progress, pr.target_repo.repo_name),
114 'name_raw': pr.pull_request_id,
114 'name_raw': pr.pull_request_id,
115 'status': _render('pullrequest_status',
115 'status': _render('pullrequest_status',
116 pr.calculated_review_status()),
116 pr.calculated_review_status()),
117 'title': _render('pullrequest_title', pr.title, pr.description),
117 'title': _render('pullrequest_title', pr.title, pr.description),
118 'description': h.escape(pr.description),
118 'description': h.escape(pr.description),
119 'updated_on': _render('pullrequest_updated_on',
119 'updated_on': _render('pullrequest_updated_on',
120 h.datetime_to_time(pr.updated_on)),
120 h.datetime_to_time(pr.updated_on)),
121 'updated_on_raw': h.datetime_to_time(pr.updated_on),
121 'updated_on_raw': h.datetime_to_time(pr.updated_on),
122 'created_on': _render('pullrequest_updated_on',
122 'created_on': _render('pullrequest_updated_on',
123 h.datetime_to_time(pr.created_on)),
123 h.datetime_to_time(pr.created_on)),
124 'created_on_raw': h.datetime_to_time(pr.created_on),
124 'created_on_raw': h.datetime_to_time(pr.created_on),
125 'state': pr.pull_request_state,
125 'state': pr.pull_request_state,
126 'author': _render('pullrequest_author',
126 'author': _render('pullrequest_author',
127 pr.author.full_contact, ),
127 pr.author.full_contact, ),
128 'author_raw': pr.author.full_name,
128 'author_raw': pr.author.full_name,
129 'comments': _render('pullrequest_comments', len(comments)),
129 'comments': _render('pullrequest_comments', len(comments)),
130 'comments_raw': len(comments),
130 'comments_raw': len(comments),
131 'closed': pr.is_closed(),
131 'closed': pr.is_closed(),
132 })
132 })
133
133
134 data = ({
134 data = ({
135 'draw': draw,
135 'draw': draw,
136 'data': data,
136 'data': data,
137 'recordsTotal': pull_requests_total_count,
137 'recordsTotal': pull_requests_total_count,
138 'recordsFiltered': pull_requests_total_count,
138 'recordsFiltered': pull_requests_total_count,
139 })
139 })
140 return data
140 return data
141
141
142 @LoginRequired()
142 @LoginRequired()
143 @HasRepoPermissionAnyDecorator(
143 @HasRepoPermissionAnyDecorator(
144 'repository.read', 'repository.write', 'repository.admin')
144 'repository.read', 'repository.write', 'repository.admin')
145 @view_config(
145 @view_config(
146 route_name='pullrequest_show_all', request_method='GET',
146 route_name='pullrequest_show_all', request_method='GET',
147 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
147 renderer='rhodecode:templates/pullrequests/pullrequests.mako')
148 def pull_request_list(self):
148 def pull_request_list(self):
149 c = self.load_default_context()
149 c = self.load_default_context()
150
150
151 req_get = self.request.GET
151 req_get = self.request.GET
152 c.source = str2bool(req_get.get('source'))
152 c.source = str2bool(req_get.get('source'))
153 c.closed = str2bool(req_get.get('closed'))
153 c.closed = str2bool(req_get.get('closed'))
154 c.my = str2bool(req_get.get('my'))
154 c.my = str2bool(req_get.get('my'))
155 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
155 c.awaiting_review = str2bool(req_get.get('awaiting_review'))
156 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
156 c.awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
157
157
158 c.active = 'open'
158 c.active = 'open'
159 if c.my:
159 if c.my:
160 c.active = 'my'
160 c.active = 'my'
161 if c.closed:
161 if c.closed:
162 c.active = 'closed'
162 c.active = 'closed'
163 if c.awaiting_review and not c.source:
163 if c.awaiting_review and not c.source:
164 c.active = 'awaiting'
164 c.active = 'awaiting'
165 if c.source and not c.awaiting_review:
165 if c.source and not c.awaiting_review:
166 c.active = 'source'
166 c.active = 'source'
167 if c.awaiting_my_review:
167 if c.awaiting_my_review:
168 c.active = 'awaiting_my'
168 c.active = 'awaiting_my'
169
169
170 return self._get_template_context(c)
170 return self._get_template_context(c)
171
171
172 @LoginRequired()
172 @LoginRequired()
173 @HasRepoPermissionAnyDecorator(
173 @HasRepoPermissionAnyDecorator(
174 'repository.read', 'repository.write', 'repository.admin')
174 'repository.read', 'repository.write', 'repository.admin')
175 @view_config(
175 @view_config(
176 route_name='pullrequest_show_all_data', request_method='GET',
176 route_name='pullrequest_show_all_data', request_method='GET',
177 renderer='json_ext', xhr=True)
177 renderer='json_ext', xhr=True)
178 def pull_request_list_data(self):
178 def pull_request_list_data(self):
179 self.load_default_context()
179 self.load_default_context()
180
180
181 # additional filters
181 # additional filters
182 req_get = self.request.GET
182 req_get = self.request.GET
183 source = str2bool(req_get.get('source'))
183 source = str2bool(req_get.get('source'))
184 closed = str2bool(req_get.get('closed'))
184 closed = str2bool(req_get.get('closed'))
185 my = str2bool(req_get.get('my'))
185 my = str2bool(req_get.get('my'))
186 awaiting_review = str2bool(req_get.get('awaiting_review'))
186 awaiting_review = str2bool(req_get.get('awaiting_review'))
187 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
187 awaiting_my_review = str2bool(req_get.get('awaiting_my_review'))
188
188
189 filter_type = 'awaiting_review' if awaiting_review \
189 filter_type = 'awaiting_review' if awaiting_review \
190 else 'awaiting_my_review' if awaiting_my_review \
190 else 'awaiting_my_review' if awaiting_my_review \
191 else None
191 else None
192
192
193 opened_by = None
193 opened_by = None
194 if my:
194 if my:
195 opened_by = [self._rhodecode_user.user_id]
195 opened_by = [self._rhodecode_user.user_id]
196
196
197 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
197 statuses = [PullRequest.STATUS_NEW, PullRequest.STATUS_OPEN]
198 if closed:
198 if closed:
199 statuses = [PullRequest.STATUS_CLOSED]
199 statuses = [PullRequest.STATUS_CLOSED]
200
200
201 data = self._get_pull_requests_list(
201 data = self._get_pull_requests_list(
202 repo_name=self.db_repo_name, source=source,
202 repo_name=self.db_repo_name, source=source,
203 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
203 filter_type=filter_type, opened_by=opened_by, statuses=statuses)
204
204
205 return data
205 return data
206
206
207 def _is_diff_cache_enabled(self, target_repo):
207 def _is_diff_cache_enabled(self, target_repo):
208 caching_enabled = self._get_general_setting(
208 caching_enabled = self._get_general_setting(
209 target_repo, 'rhodecode_diff_cache')
209 target_repo, 'rhodecode_diff_cache')
210 log.debug('Diff caching enabled: %s', caching_enabled)
210 log.debug('Diff caching enabled: %s', caching_enabled)
211 return caching_enabled
211 return caching_enabled
212
212
213 def _get_diffset(self, source_repo_name, source_repo,
213 def _get_diffset(self, source_repo_name, source_repo,
214 ancestor_commit,
214 ancestor_commit,
215 source_ref_id, target_ref_id,
215 source_ref_id, target_ref_id,
216 target_commit, source_commit, diff_limit, file_limit,
216 target_commit, source_commit, diff_limit, file_limit,
217 fulldiff, hide_whitespace_changes, diff_context, use_ancestor=True):
217 fulldiff, hide_whitespace_changes, diff_context, use_ancestor=True):
218
218
219 if use_ancestor:
219 if use_ancestor:
220 # we might want to not use it for versions
220 # we might want to not use it for versions
221 target_ref_id = ancestor_commit.raw_id
221 target_ref_id = ancestor_commit.raw_id
222
222
223 vcs_diff = PullRequestModel().get_diff(
223 vcs_diff = PullRequestModel().get_diff(
224 source_repo, source_ref_id, target_ref_id,
224 source_repo, source_ref_id, target_ref_id,
225 hide_whitespace_changes, diff_context)
225 hide_whitespace_changes, diff_context)
226
226
227 diff_processor = diffs.DiffProcessor(
227 diff_processor = diffs.DiffProcessor(
228 vcs_diff, format='newdiff', diff_limit=diff_limit,
228 vcs_diff, format='newdiff', diff_limit=diff_limit,
229 file_limit=file_limit, show_full_diff=fulldiff)
229 file_limit=file_limit, show_full_diff=fulldiff)
230
230
231 _parsed = diff_processor.prepare()
231 _parsed = diff_processor.prepare()
232
232
233 diffset = codeblocks.DiffSet(
233 diffset = codeblocks.DiffSet(
234 repo_name=self.db_repo_name,
234 repo_name=self.db_repo_name,
235 source_repo_name=source_repo_name,
235 source_repo_name=source_repo_name,
236 source_node_getter=codeblocks.diffset_node_getter(target_commit),
236 source_node_getter=codeblocks.diffset_node_getter(target_commit),
237 target_node_getter=codeblocks.diffset_node_getter(source_commit),
237 target_node_getter=codeblocks.diffset_node_getter(source_commit),
238 )
238 )
239 diffset = self.path_filter.render_patchset_filtered(
239 diffset = self.path_filter.render_patchset_filtered(
240 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
240 diffset, _parsed, target_commit.raw_id, source_commit.raw_id)
241
241
242 return diffset
242 return diffset
243
243
244 def _get_range_diffset(self, source_scm, source_repo,
244 def _get_range_diffset(self, source_scm, source_repo,
245 commit1, commit2, diff_limit, file_limit,
245 commit1, commit2, diff_limit, file_limit,
246 fulldiff, hide_whitespace_changes, diff_context):
246 fulldiff, hide_whitespace_changes, diff_context):
247 vcs_diff = source_scm.get_diff(
247 vcs_diff = source_scm.get_diff(
248 commit1, commit2,
248 commit1, commit2,
249 ignore_whitespace=hide_whitespace_changes,
249 ignore_whitespace=hide_whitespace_changes,
250 context=diff_context)
250 context=diff_context)
251
251
252 diff_processor = diffs.DiffProcessor(
252 diff_processor = diffs.DiffProcessor(
253 vcs_diff, format='newdiff', diff_limit=diff_limit,
253 vcs_diff, format='newdiff', diff_limit=diff_limit,
254 file_limit=file_limit, show_full_diff=fulldiff)
254 file_limit=file_limit, show_full_diff=fulldiff)
255
255
256 _parsed = diff_processor.prepare()
256 _parsed = diff_processor.prepare()
257
257
258 diffset = codeblocks.DiffSet(
258 diffset = codeblocks.DiffSet(
259 repo_name=source_repo.repo_name,
259 repo_name=source_repo.repo_name,
260 source_node_getter=codeblocks.diffset_node_getter(commit1),
260 source_node_getter=codeblocks.diffset_node_getter(commit1),
261 target_node_getter=codeblocks.diffset_node_getter(commit2))
261 target_node_getter=codeblocks.diffset_node_getter(commit2))
262
262
263 diffset = self.path_filter.render_patchset_filtered(
263 diffset = self.path_filter.render_patchset_filtered(
264 diffset, _parsed, commit1.raw_id, commit2.raw_id)
264 diffset, _parsed, commit1.raw_id, commit2.raw_id)
265
265
266 return diffset
266 return diffset
267
267
268 def register_comments_vars(self, c, pull_request, versions):
268 def register_comments_vars(self, c, pull_request, versions):
269 comments_model = CommentsModel()
269 comments_model = CommentsModel()
270
270
271 # GENERAL COMMENTS with versions #
271 # GENERAL COMMENTS with versions #
272 q = comments_model._all_general_comments_of_pull_request(pull_request)
272 q = comments_model._all_general_comments_of_pull_request(pull_request)
273 q = q.order_by(ChangesetComment.comment_id.asc())
273 q = q.order_by(ChangesetComment.comment_id.asc())
274 general_comments = q
274 general_comments = q
275
275
276 # pick comments we want to render at current version
276 # pick comments we want to render at current version
277 c.comment_versions = comments_model.aggregate_comments(
277 c.comment_versions = comments_model.aggregate_comments(
278 general_comments, versions, c.at_version_num)
278 general_comments, versions, c.at_version_num)
279
279
280 # INLINE COMMENTS with versions #
280 # INLINE COMMENTS with versions #
281 q = comments_model._all_inline_comments_of_pull_request(pull_request)
281 q = comments_model._all_inline_comments_of_pull_request(pull_request)
282 q = q.order_by(ChangesetComment.comment_id.asc())
282 q = q.order_by(ChangesetComment.comment_id.asc())
283 inline_comments = q
283 inline_comments = q
284
284
285 c.inline_versions = comments_model.aggregate_comments(
285 c.inline_versions = comments_model.aggregate_comments(
286 inline_comments, versions, c.at_version_num, inline=True)
286 inline_comments, versions, c.at_version_num, inline=True)
287
287
288 # Comments inline+general
288 # Comments inline+general
289 if c.at_version:
289 if c.at_version:
290 c.inline_comments_flat = c.inline_versions[c.at_version_num]['display']
290 c.inline_comments_flat = c.inline_versions[c.at_version_num]['display']
291 c.comments = c.comment_versions[c.at_version_num]['display']
291 c.comments = c.comment_versions[c.at_version_num]['display']
292 else:
292 else:
293 c.inline_comments_flat = c.inline_versions[c.at_version_num]['until']
293 c.inline_comments_flat = c.inline_versions[c.at_version_num]['until']
294 c.comments = c.comment_versions[c.at_version_num]['until']
294 c.comments = c.comment_versions[c.at_version_num]['until']
295
295
296 return general_comments, inline_comments
296 return general_comments, inline_comments
297
297
298 @LoginRequired()
298 @LoginRequired()
299 @HasRepoPermissionAnyDecorator(
299 @HasRepoPermissionAnyDecorator(
300 'repository.read', 'repository.write', 'repository.admin')
300 'repository.read', 'repository.write', 'repository.admin')
301 @view_config(
301 @view_config(
302 route_name='pullrequest_show', request_method='GET',
302 route_name='pullrequest_show', request_method='GET',
303 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
303 renderer='rhodecode:templates/pullrequests/pullrequest_show.mako')
304 def pull_request_show(self):
304 def pull_request_show(self):
305 _ = self.request.translate
305 _ = self.request.translate
306 c = self.load_default_context()
306 c = self.load_default_context()
307
307
308 pull_request = PullRequest.get_or_404(
308 pull_request = PullRequest.get_or_404(
309 self.request.matchdict['pull_request_id'])
309 self.request.matchdict['pull_request_id'])
310 pull_request_id = pull_request.pull_request_id
310 pull_request_id = pull_request.pull_request_id
311
311
312 c.state_progressing = pull_request.is_state_changing()
312 c.state_progressing = pull_request.is_state_changing()
313 c.pr_broadcast_channel = '/repo${}$/pr/{}'.format(
313 c.pr_broadcast_channel = '/repo${}$/pr/{}'.format(
314 pull_request.target_repo.repo_name, pull_request.pull_request_id)
314 pull_request.target_repo.repo_name, pull_request.pull_request_id)
315
315
316 _new_state = {
316 _new_state = {
317 'created': PullRequest.STATE_CREATED,
317 'created': PullRequest.STATE_CREATED,
318 }.get(self.request.GET.get('force_state'))
318 }.get(self.request.GET.get('force_state'))
319
319
320 if c.is_super_admin and _new_state:
320 if c.is_super_admin and _new_state:
321 with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state):
321 with pull_request.set_state(PullRequest.STATE_UPDATING, final_state=_new_state):
322 h.flash(
322 h.flash(
323 _('Pull Request state was force changed to `{}`').format(_new_state),
323 _('Pull Request state was force changed to `{}`').format(_new_state),
324 category='success')
324 category='success')
325 Session().commit()
325 Session().commit()
326
326
327 raise HTTPFound(h.route_path(
327 raise HTTPFound(h.route_path(
328 'pullrequest_show', repo_name=self.db_repo_name,
328 'pullrequest_show', repo_name=self.db_repo_name,
329 pull_request_id=pull_request_id))
329 pull_request_id=pull_request_id))
330
330
331 version = self.request.GET.get('version')
331 version = self.request.GET.get('version')
332 from_version = self.request.GET.get('from_version') or version
332 from_version = self.request.GET.get('from_version') or version
333 merge_checks = self.request.GET.get('merge_checks')
333 merge_checks = self.request.GET.get('merge_checks')
334 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
334 c.fulldiff = str2bool(self.request.GET.get('fulldiff'))
335 force_refresh = str2bool(self.request.GET.get('force_refresh'))
335 force_refresh = str2bool(self.request.GET.get('force_refresh'))
336 c.range_diff_on = self.request.GET.get('range-diff') == "1"
336 c.range_diff_on = self.request.GET.get('range-diff') == "1"
337
337
338 # fetch global flags of ignore ws or context lines
338 # fetch global flags of ignore ws or context lines
339 diff_context = diffs.get_diff_context(self.request)
339 diff_context = diffs.get_diff_context(self.request)
340 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
340 hide_whitespace_changes = diffs.get_diff_whitespace_flag(self.request)
341
341
342 (pull_request_latest,
342 (pull_request_latest,
343 pull_request_at_ver,
343 pull_request_at_ver,
344 pull_request_display_obj,
344 pull_request_display_obj,
345 at_version) = PullRequestModel().get_pr_version(
345 at_version) = PullRequestModel().get_pr_version(
346 pull_request_id, version=version)
346 pull_request_id, version=version)
347
347
348 pr_closed = pull_request_latest.is_closed()
348 pr_closed = pull_request_latest.is_closed()
349
349
350 if pr_closed and (version or from_version):
350 if pr_closed and (version or from_version):
351 # not allow to browse versions for closed PR
351 # not allow to browse versions for closed PR
352 raise HTTPFound(h.route_path(
352 raise HTTPFound(h.route_path(
353 'pullrequest_show', repo_name=self.db_repo_name,
353 'pullrequest_show', repo_name=self.db_repo_name,
354 pull_request_id=pull_request_id))
354 pull_request_id=pull_request_id))
355
355
356 versions = pull_request_display_obj.versions()
356 versions = pull_request_display_obj.versions()
357 # used to store per-commit range diffs
357 # used to store per-commit range diffs
358 c.changes = collections.OrderedDict()
358 c.changes = collections.OrderedDict()
359
359
360 c.at_version = at_version
360 c.at_version = at_version
361 c.at_version_num = (at_version
361 c.at_version_num = (at_version
362 if at_version and at_version != PullRequest.LATEST_VER
362 if at_version and at_version != PullRequest.LATEST_VER
363 else None)
363 else None)
364
364
365 c.at_version_index = ChangesetComment.get_index_from_version(
365 c.at_version_index = ChangesetComment.get_index_from_version(
366 c.at_version_num, versions)
366 c.at_version_num, versions)
367
367
368 (prev_pull_request_latest,
368 (prev_pull_request_latest,
369 prev_pull_request_at_ver,
369 prev_pull_request_at_ver,
370 prev_pull_request_display_obj,
370 prev_pull_request_display_obj,
371 prev_at_version) = PullRequestModel().get_pr_version(
371 prev_at_version) = PullRequestModel().get_pr_version(
372 pull_request_id, version=from_version)
372 pull_request_id, version=from_version)
373
373
374 c.from_version = prev_at_version
374 c.from_version = prev_at_version
375 c.from_version_num = (prev_at_version
375 c.from_version_num = (prev_at_version
376 if prev_at_version and prev_at_version != PullRequest.LATEST_VER
376 if prev_at_version and prev_at_version != PullRequest.LATEST_VER
377 else None)
377 else None)
378 c.from_version_index = ChangesetComment.get_index_from_version(
378 c.from_version_index = ChangesetComment.get_index_from_version(
379 c.from_version_num, versions)
379 c.from_version_num, versions)
380
380
381 # define if we're in COMPARE mode or VIEW at version mode
381 # define if we're in COMPARE mode or VIEW at version mode
382 compare = at_version != prev_at_version
382 compare = at_version != prev_at_version
383
383
384 # pull_requests repo_name we opened it against
384 # pull_requests repo_name we opened it against
385 # ie. target_repo must match
385 # ie. target_repo must match
386 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
386 if self.db_repo_name != pull_request_at_ver.target_repo.repo_name:
387 log.warning('Mismatch between the current repo: %s, and target %s',
387 log.warning('Mismatch between the current repo: %s, and target %s',
388 self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
388 self.db_repo_name, pull_request_at_ver.target_repo.repo_name)
389 raise HTTPNotFound()
389 raise HTTPNotFound()
390
390
391 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver)
391 c.shadow_clone_url = PullRequestModel().get_shadow_clone_url(pull_request_at_ver)
392
392
393 c.pull_request = pull_request_display_obj
393 c.pull_request = pull_request_display_obj
394 c.renderer = pull_request_at_ver.description_renderer or c.renderer
394 c.renderer = pull_request_at_ver.description_renderer or c.renderer
395 c.pull_request_latest = pull_request_latest
395 c.pull_request_latest = pull_request_latest
396
396
397 # inject latest version
397 # inject latest version
398 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
398 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
399 c.versions = versions + [latest_ver]
399 c.versions = versions + [latest_ver]
400
400
401 if compare or (at_version and not at_version == PullRequest.LATEST_VER):
401 if compare or (at_version and not at_version == PullRequest.LATEST_VER):
402 c.allowed_to_change_status = False
402 c.allowed_to_change_status = False
403 c.allowed_to_update = False
403 c.allowed_to_update = False
404 c.allowed_to_merge = False
404 c.allowed_to_merge = False
405 c.allowed_to_delete = False
405 c.allowed_to_delete = False
406 c.allowed_to_comment = False
406 c.allowed_to_comment = False
407 c.allowed_to_close = False
407 c.allowed_to_close = False
408 else:
408 else:
409 can_change_status = PullRequestModel().check_user_change_status(
409 can_change_status = PullRequestModel().check_user_change_status(
410 pull_request_at_ver, self._rhodecode_user)
410 pull_request_at_ver, self._rhodecode_user)
411 c.allowed_to_change_status = can_change_status and not pr_closed
411 c.allowed_to_change_status = can_change_status and not pr_closed
412
412
413 c.allowed_to_update = PullRequestModel().check_user_update(
413 c.allowed_to_update = PullRequestModel().check_user_update(
414 pull_request_latest, self._rhodecode_user) and not pr_closed
414 pull_request_latest, self._rhodecode_user) and not pr_closed
415 c.allowed_to_merge = PullRequestModel().check_user_merge(
415 c.allowed_to_merge = PullRequestModel().check_user_merge(
416 pull_request_latest, self._rhodecode_user) and not pr_closed
416 pull_request_latest, self._rhodecode_user) and not pr_closed
417 c.allowed_to_delete = PullRequestModel().check_user_delete(
417 c.allowed_to_delete = PullRequestModel().check_user_delete(
418 pull_request_latest, self._rhodecode_user) and not pr_closed
418 pull_request_latest, self._rhodecode_user) and not pr_closed
419 c.allowed_to_comment = not pr_closed
419 c.allowed_to_comment = not pr_closed
420 c.allowed_to_close = c.allowed_to_merge and not pr_closed
420 c.allowed_to_close = c.allowed_to_merge and not pr_closed
421
421
422 c.forbid_adding_reviewers = False
422 c.forbid_adding_reviewers = False
423 c.forbid_author_to_review = False
423 c.forbid_author_to_review = False
424 c.forbid_commit_author_to_review = False
424 c.forbid_commit_author_to_review = False
425
425
426 if pull_request_latest.reviewer_data and \
426 if pull_request_latest.reviewer_data and \
427 'rules' in pull_request_latest.reviewer_data:
427 'rules' in pull_request_latest.reviewer_data:
428 rules = pull_request_latest.reviewer_data['rules'] or {}
428 rules = pull_request_latest.reviewer_data['rules'] or {}
429 try:
429 try:
430 c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
430 c.forbid_adding_reviewers = rules.get('forbid_adding_reviewers')
431 c.forbid_author_to_review = rules.get('forbid_author_to_review')
431 c.forbid_author_to_review = rules.get('forbid_author_to_review')
432 c.forbid_commit_author_to_review = rules.get('forbid_commit_author_to_review')
432 c.forbid_commit_author_to_review = rules.get('forbid_commit_author_to_review')
433 except Exception:
433 except Exception:
434 pass
434 pass
435
435
436 # check merge capabilities
436 # check merge capabilities
437 _merge_check = MergeCheck.validate(
437 _merge_check = MergeCheck.validate(
438 pull_request_latest, auth_user=self._rhodecode_user,
438 pull_request_latest, auth_user=self._rhodecode_user,
439 translator=self.request.translate,
439 translator=self.request.translate,
440 force_shadow_repo_refresh=force_refresh)
440 force_shadow_repo_refresh=force_refresh)
441
441
442 c.pr_merge_errors = _merge_check.error_details
442 c.pr_merge_errors = _merge_check.error_details
443 c.pr_merge_possible = not _merge_check.failed
443 c.pr_merge_possible = not _merge_check.failed
444 c.pr_merge_message = _merge_check.merge_msg
444 c.pr_merge_message = _merge_check.merge_msg
445 c.pr_merge_source_commit = _merge_check.source_commit
445 c.pr_merge_source_commit = _merge_check.source_commit
446 c.pr_merge_target_commit = _merge_check.target_commit
446 c.pr_merge_target_commit = _merge_check.target_commit
447
447
448 c.pr_merge_info = MergeCheck.get_merge_conditions(
448 c.pr_merge_info = MergeCheck.get_merge_conditions(
449 pull_request_latest, translator=self.request.translate)
449 pull_request_latest, translator=self.request.translate)
450
450
451 c.pull_request_review_status = _merge_check.review_status
451 c.pull_request_review_status = _merge_check.review_status
452 if merge_checks:
452 if merge_checks:
453 self.request.override_renderer = \
453 self.request.override_renderer = \
454 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
454 'rhodecode:templates/pullrequests/pullrequest_merge_checks.mako'
455 return self._get_template_context(c)
455 return self._get_template_context(c)
456
456
457 c.allowed_reviewers = [obj.user_id for obj in pull_request.reviewers if obj.user]
457 c.allowed_reviewers = [obj.user_id for obj in pull_request.reviewers if obj.user]
458
458
459 # reviewers and statuses
459 # reviewers and statuses
460 c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
460 c.pull_request_default_reviewers_data_json = json.dumps(pull_request.reviewer_data)
461 c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
461 c.pull_request_set_reviewers_data_json = collections.OrderedDict({'reviewers': []})
462
462
463 for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
463 for review_obj, member, reasons, mandatory, status in pull_request_at_ver.reviewers_statuses():
464 member_reviewer = h.reviewer_as_json(
464 member_reviewer = h.reviewer_as_json(
465 member, reasons=reasons, mandatory=mandatory,
465 member, reasons=reasons, mandatory=mandatory,
466 user_group=review_obj.rule_user_group_data()
466 user_group=review_obj.rule_user_group_data()
467 )
467 )
468
468
469 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
469 current_review_status = status[0][1].status if status else ChangesetStatus.STATUS_NOT_REVIEWED
470 member_reviewer['review_status'] = current_review_status
470 member_reviewer['review_status'] = current_review_status
471 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
471 member_reviewer['review_status_label'] = h.commit_status_lbl(current_review_status)
472 member_reviewer['allowed_to_update'] = c.allowed_to_update
472 member_reviewer['allowed_to_update'] = c.allowed_to_update
473 c.pull_request_set_reviewers_data_json['reviewers'].append(member_reviewer)
473 c.pull_request_set_reviewers_data_json['reviewers'].append(member_reviewer)
474
474
475 c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
475 c.pull_request_set_reviewers_data_json = json.dumps(c.pull_request_set_reviewers_data_json)
476
476
477
478
479
480 general_comments, inline_comments = \
477 general_comments, inline_comments = \
481 self.register_comments_vars(c, pull_request_latest, versions)
478 self.register_comments_vars(c, pull_request_latest, versions)
482
479
483 # TODOs
480 # TODOs
484 c.unresolved_comments = CommentsModel() \
481 c.unresolved_comments = CommentsModel() \
485 .get_pull_request_unresolved_todos(pull_request_latest)
482 .get_pull_request_unresolved_todos(pull_request_latest)
486 c.resolved_comments = CommentsModel() \
483 c.resolved_comments = CommentsModel() \
487 .get_pull_request_resolved_todos(pull_request_latest)
484 .get_pull_request_resolved_todos(pull_request_latest)
488
485
489 # if we use version, then do not show later comments
486 # if we use version, then do not show later comments
490 # than current version
487 # than current version
491 display_inline_comments = collections.defaultdict(
488 display_inline_comments = collections.defaultdict(
492 lambda: collections.defaultdict(list))
489 lambda: collections.defaultdict(list))
493 for co in inline_comments:
490 for co in inline_comments:
494 if c.at_version_num:
491 if c.at_version_num:
495 # pick comments that are at least UPTO given version, so we
492 # pick comments that are at least UPTO given version, so we
496 # don't render comments for higher version
493 # don't render comments for higher version
497 should_render = co.pull_request_version_id and \
494 should_render = co.pull_request_version_id and \
498 co.pull_request_version_id <= c.at_version_num
495 co.pull_request_version_id <= c.at_version_num
499 else:
496 else:
500 # showing all, for 'latest'
497 # showing all, for 'latest'
501 should_render = True
498 should_render = True
502
499
503 if should_render:
500 if should_render:
504 display_inline_comments[co.f_path][co.line_no].append(co)
501 display_inline_comments[co.f_path][co.line_no].append(co)
505
502
506 # load diff data into template context, if we use compare mode then
503 # load diff data into template context, if we use compare mode then
507 # diff is calculated based on changes between versions of PR
504 # diff is calculated based on changes between versions of PR
508
505
509 source_repo = pull_request_at_ver.source_repo
506 source_repo = pull_request_at_ver.source_repo
510 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
507 source_ref_id = pull_request_at_ver.source_ref_parts.commit_id
511
508
512 target_repo = pull_request_at_ver.target_repo
509 target_repo = pull_request_at_ver.target_repo
513 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
510 target_ref_id = pull_request_at_ver.target_ref_parts.commit_id
514
511
515 if compare:
512 if compare:
516 # in compare switch the diff base to latest commit from prev version
513 # in compare switch the diff base to latest commit from prev version
517 target_ref_id = prev_pull_request_display_obj.revisions[0]
514 target_ref_id = prev_pull_request_display_obj.revisions[0]
518
515
519 # despite opening commits for bookmarks/branches/tags, we always
516 # despite opening commits for bookmarks/branches/tags, we always
520 # convert this to rev to prevent changes after bookmark or branch change
517 # convert this to rev to prevent changes after bookmark or branch change
521 c.source_ref_type = 'rev'
518 c.source_ref_type = 'rev'
522 c.source_ref = source_ref_id
519 c.source_ref = source_ref_id
523
520
524 c.target_ref_type = 'rev'
521 c.target_ref_type = 'rev'
525 c.target_ref = target_ref_id
522 c.target_ref = target_ref_id
526
523
527 c.source_repo = source_repo
524 c.source_repo = source_repo
528 c.target_repo = target_repo
525 c.target_repo = target_repo
529
526
530 c.commit_ranges = []
527 c.commit_ranges = []
531 source_commit = EmptyCommit()
528 source_commit = EmptyCommit()
532 target_commit = EmptyCommit()
529 target_commit = EmptyCommit()
533 c.missing_requirements = False
530 c.missing_requirements = False
534
531
535 source_scm = source_repo.scm_instance()
532 source_scm = source_repo.scm_instance()
536 target_scm = target_repo.scm_instance()
533 target_scm = target_repo.scm_instance()
537
534
538 shadow_scm = None
535 shadow_scm = None
539 try:
536 try:
540 shadow_scm = pull_request_latest.get_shadow_repo()
537 shadow_scm = pull_request_latest.get_shadow_repo()
541 except Exception:
538 except Exception:
542 log.debug('Failed to get shadow repo', exc_info=True)
539 log.debug('Failed to get shadow repo', exc_info=True)
543 # try first the existing source_repo, and then shadow
540 # try first the existing source_repo, and then shadow
544 # repo if we can obtain one
541 # repo if we can obtain one
545 commits_source_repo = source_scm
542 commits_source_repo = source_scm
546 if shadow_scm:
543 if shadow_scm:
547 commits_source_repo = shadow_scm
544 commits_source_repo = shadow_scm
548
545
549 c.commits_source_repo = commits_source_repo
546 c.commits_source_repo = commits_source_repo
550 c.ancestor = None # set it to None, to hide it from PR view
547 c.ancestor = None # set it to None, to hide it from PR view
551
548
552 # empty version means latest, so we keep this to prevent
549 # empty version means latest, so we keep this to prevent
553 # double caching
550 # double caching
554 version_normalized = version or PullRequest.LATEST_VER
551 version_normalized = version or PullRequest.LATEST_VER
555 from_version_normalized = from_version or PullRequest.LATEST_VER
552 from_version_normalized = from_version or PullRequest.LATEST_VER
556
553
557 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
554 cache_path = self.rhodecode_vcs_repo.get_create_shadow_cache_pr_path(target_repo)
558 cache_file_path = diff_cache_exist(
555 cache_file_path = diff_cache_exist(
559 cache_path, 'pull_request', pull_request_id, version_normalized,
556 cache_path, 'pull_request', pull_request_id, version_normalized,
560 from_version_normalized, source_ref_id, target_ref_id,
557 from_version_normalized, source_ref_id, target_ref_id,
561 hide_whitespace_changes, diff_context, c.fulldiff)
558 hide_whitespace_changes, diff_context, c.fulldiff)
562
559
563 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
560 caching_enabled = self._is_diff_cache_enabled(c.target_repo)
564 force_recache = self.get_recache_flag()
561 force_recache = self.get_recache_flag()
565
562
566 cached_diff = None
563 cached_diff = None
567 if caching_enabled:
564 if caching_enabled:
568 cached_diff = load_cached_diff(cache_file_path)
565 cached_diff = load_cached_diff(cache_file_path)
569
566
570 has_proper_commit_cache = (
567 has_proper_commit_cache = (
571 cached_diff and cached_diff.get('commits')
568 cached_diff and cached_diff.get('commits')
572 and len(cached_diff.get('commits', [])) == 5
569 and len(cached_diff.get('commits', [])) == 5
573 and cached_diff.get('commits')[0]
570 and cached_diff.get('commits')[0]
574 and cached_diff.get('commits')[3])
571 and cached_diff.get('commits')[3])
575
572
576 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
573 if not force_recache and not c.range_diff_on and has_proper_commit_cache:
577 diff_commit_cache = \
574 diff_commit_cache = \
578 (ancestor_commit, commit_cache, missing_requirements,
575 (ancestor_commit, commit_cache, missing_requirements,
579 source_commit, target_commit) = cached_diff['commits']
576 source_commit, target_commit) = cached_diff['commits']
580 else:
577 else:
581 # NOTE(marcink): we reach potentially unreachable errors when a PR has
578 # NOTE(marcink): we reach potentially unreachable errors when a PR has
582 # merge errors resulting in potentially hidden commits in the shadow repo.
579 # merge errors resulting in potentially hidden commits in the shadow repo.
583 maybe_unreachable = _merge_check.MERGE_CHECK in _merge_check.error_details \
580 maybe_unreachable = _merge_check.MERGE_CHECK in _merge_check.error_details \
584 and _merge_check.merge_response
581 and _merge_check.merge_response
585 maybe_unreachable = maybe_unreachable \
582 maybe_unreachable = maybe_unreachable \
586 and _merge_check.merge_response.metadata.get('unresolved_files')
583 and _merge_check.merge_response.metadata.get('unresolved_files')
587 log.debug("Using unreachable commits due to MERGE_CHECK in merge simulation")
584 log.debug("Using unreachable commits due to MERGE_CHECK in merge simulation")
588 diff_commit_cache = \
585 diff_commit_cache = \
589 (ancestor_commit, commit_cache, missing_requirements,
586 (ancestor_commit, commit_cache, missing_requirements,
590 source_commit, target_commit) = self.get_commits(
587 source_commit, target_commit) = self.get_commits(
591 commits_source_repo,
588 commits_source_repo,
592 pull_request_at_ver,
589 pull_request_at_ver,
593 source_commit,
590 source_commit,
594 source_ref_id,
591 source_ref_id,
595 source_scm,
592 source_scm,
596 target_commit,
593 target_commit,
597 target_ref_id,
594 target_ref_id,
598 target_scm,
595 target_scm,
599 maybe_unreachable=maybe_unreachable)
596 maybe_unreachable=maybe_unreachable)
600
597
601 # register our commit range
598 # register our commit range
602 for comm in commit_cache.values():
599 for comm in commit_cache.values():
603 c.commit_ranges.append(comm)
600 c.commit_ranges.append(comm)
604
601
605 c.missing_requirements = missing_requirements
602 c.missing_requirements = missing_requirements
606 c.ancestor_commit = ancestor_commit
603 c.ancestor_commit = ancestor_commit
607 c.statuses = source_repo.statuses(
604 c.statuses = source_repo.statuses(
608 [x.raw_id for x in c.commit_ranges])
605 [x.raw_id for x in c.commit_ranges])
609
606
610 # auto collapse if we have more than limit
607 # auto collapse if we have more than limit
611 collapse_limit = diffs.DiffProcessor._collapse_commits_over
608 collapse_limit = diffs.DiffProcessor._collapse_commits_over
612 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
609 c.collapse_all_commits = len(c.commit_ranges) > collapse_limit
613 c.compare_mode = compare
610 c.compare_mode = compare
614
611
615 # diff_limit is the old behavior, will cut off the whole diff
612 # diff_limit is the old behavior, will cut off the whole diff
616 # if the limit is applied otherwise will just hide the
613 # if the limit is applied otherwise will just hide the
617 # big files from the front-end
614 # big files from the front-end
618 diff_limit = c.visual.cut_off_limit_diff
615 diff_limit = c.visual.cut_off_limit_diff
619 file_limit = c.visual.cut_off_limit_file
616 file_limit = c.visual.cut_off_limit_file
620
617
621 c.missing_commits = False
618 c.missing_commits = False
622 if (c.missing_requirements
619 if (c.missing_requirements
623 or isinstance(source_commit, EmptyCommit)
620 or isinstance(source_commit, EmptyCommit)
624 or source_commit == target_commit):
621 or source_commit == target_commit):
625
622
626 c.missing_commits = True
623 c.missing_commits = True
627 else:
624 else:
628 c.inline_comments = display_inline_comments
625 c.inline_comments = display_inline_comments
629
626
630 use_ancestor = True
627 use_ancestor = True
631 if from_version_normalized != version_normalized:
628 if from_version_normalized != version_normalized:
632 use_ancestor = False
629 use_ancestor = False
633
630
634 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
631 has_proper_diff_cache = cached_diff and cached_diff.get('commits')
635 if not force_recache and has_proper_diff_cache:
632 if not force_recache and has_proper_diff_cache:
636 c.diffset = cached_diff['diff']
633 c.diffset = cached_diff['diff']
637 else:
634 else:
638 try:
635 try:
639 c.diffset = self._get_diffset(
636 c.diffset = self._get_diffset(
640 c.source_repo.repo_name, commits_source_repo,
637 c.source_repo.repo_name, commits_source_repo,
641 c.ancestor_commit,
638 c.ancestor_commit,
642 source_ref_id, target_ref_id,
639 source_ref_id, target_ref_id,
643 target_commit, source_commit,
640 target_commit, source_commit,
644 diff_limit, file_limit, c.fulldiff,
641 diff_limit, file_limit, c.fulldiff,
645 hide_whitespace_changes, diff_context,
642 hide_whitespace_changes, diff_context,
646 use_ancestor=use_ancestor
643 use_ancestor=use_ancestor
647 )
644 )
648
645
649 # save cached diff
646 # save cached diff
650 if caching_enabled:
647 if caching_enabled:
651 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
648 cache_diff(cache_file_path, c.diffset, diff_commit_cache)
652 except CommitDoesNotExistError:
649 except CommitDoesNotExistError:
653 log.exception('Failed to generate diffset')
650 log.exception('Failed to generate diffset')
654 c.missing_commits = True
651 c.missing_commits = True
655
652
656 if not c.missing_commits:
653 if not c.missing_commits:
657
654
658 c.limited_diff = c.diffset.limited_diff
655 c.limited_diff = c.diffset.limited_diff
659
656
660 # calculate removed files that are bound to comments
657 # calculate removed files that are bound to comments
661 comment_deleted_files = [
658 comment_deleted_files = [
662 fname for fname in display_inline_comments
659 fname for fname in display_inline_comments
663 if fname not in c.diffset.file_stats]
660 if fname not in c.diffset.file_stats]
664
661
665 c.deleted_files_comments = collections.defaultdict(dict)
662 c.deleted_files_comments = collections.defaultdict(dict)
666 for fname, per_line_comments in display_inline_comments.items():
663 for fname, per_line_comments in display_inline_comments.items():
667 if fname in comment_deleted_files:
664 if fname in comment_deleted_files:
668 c.deleted_files_comments[fname]['stats'] = 0
665 c.deleted_files_comments[fname]['stats'] = 0
669 c.deleted_files_comments[fname]['comments'] = list()
666 c.deleted_files_comments[fname]['comments'] = list()
670 for lno, comments in per_line_comments.items():
667 for lno, comments in per_line_comments.items():
671 c.deleted_files_comments[fname]['comments'].extend(comments)
668 c.deleted_files_comments[fname]['comments'].extend(comments)
672
669
673 # maybe calculate the range diff
670 # maybe calculate the range diff
674 if c.range_diff_on:
671 if c.range_diff_on:
675 # TODO(marcink): set whitespace/context
672 # TODO(marcink): set whitespace/context
676 context_lcl = 3
673 context_lcl = 3
677 ign_whitespace_lcl = False
674 ign_whitespace_lcl = False
678
675
679 for commit in c.commit_ranges:
676 for commit in c.commit_ranges:
680 commit2 = commit
677 commit2 = commit
681 commit1 = commit.first_parent
678 commit1 = commit.first_parent
682
679
683 range_diff_cache_file_path = diff_cache_exist(
680 range_diff_cache_file_path = diff_cache_exist(
684 cache_path, 'diff', commit.raw_id,
681 cache_path, 'diff', commit.raw_id,
685 ign_whitespace_lcl, context_lcl, c.fulldiff)
682 ign_whitespace_lcl, context_lcl, c.fulldiff)
686
683
687 cached_diff = None
684 cached_diff = None
688 if caching_enabled:
685 if caching_enabled:
689 cached_diff = load_cached_diff(range_diff_cache_file_path)
686 cached_diff = load_cached_diff(range_diff_cache_file_path)
690
687
691 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
688 has_proper_diff_cache = cached_diff and cached_diff.get('diff')
692 if not force_recache and has_proper_diff_cache:
689 if not force_recache and has_proper_diff_cache:
693 diffset = cached_diff['diff']
690 diffset = cached_diff['diff']
694 else:
691 else:
695 diffset = self._get_range_diffset(
692 diffset = self._get_range_diffset(
696 commits_source_repo, source_repo,
693 commits_source_repo, source_repo,
697 commit1, commit2, diff_limit, file_limit,
694 commit1, commit2, diff_limit, file_limit,
698 c.fulldiff, ign_whitespace_lcl, context_lcl
695 c.fulldiff, ign_whitespace_lcl, context_lcl
699 )
696 )
700
697
701 # save cached diff
698 # save cached diff
702 if caching_enabled:
699 if caching_enabled:
703 cache_diff(range_diff_cache_file_path, diffset, None)
700 cache_diff(range_diff_cache_file_path, diffset, None)
704
701
705 c.changes[commit.raw_id] = diffset
702 c.changes[commit.raw_id] = diffset
706
703
707 # this is a hack to properly display links, when creating PR, the
704 # this is a hack to properly display links, when creating PR, the
708 # compare view and others uses different notation, and
705 # compare view and others uses different notation, and
709 # compare_commits.mako renders links based on the target_repo.
706 # compare_commits.mako renders links based on the target_repo.
710 # We need to swap that here to generate it properly on the html side
707 # We need to swap that here to generate it properly on the html side
711 c.target_repo = c.source_repo
708 c.target_repo = c.source_repo
712
709
713 c.commit_statuses = ChangesetStatus.STATUSES
710 c.commit_statuses = ChangesetStatus.STATUSES
714
711
715 c.show_version_changes = not pr_closed
712 c.show_version_changes = not pr_closed
716 if c.show_version_changes:
713 if c.show_version_changes:
717 cur_obj = pull_request_at_ver
714 cur_obj = pull_request_at_ver
718 prev_obj = prev_pull_request_at_ver
715 prev_obj = prev_pull_request_at_ver
719
716
720 old_commit_ids = prev_obj.revisions
717 old_commit_ids = prev_obj.revisions
721 new_commit_ids = cur_obj.revisions
718 new_commit_ids = cur_obj.revisions
722 commit_changes = PullRequestModel()._calculate_commit_id_changes(
719 commit_changes = PullRequestModel()._calculate_commit_id_changes(
723 old_commit_ids, new_commit_ids)
720 old_commit_ids, new_commit_ids)
724 c.commit_changes_summary = commit_changes
721 c.commit_changes_summary = commit_changes
725
722
726 # calculate the diff for commits between versions
723 # calculate the diff for commits between versions
727 c.commit_changes = []
724 c.commit_changes = []
728
725
729 def mark(cs, fw):
726 def mark(cs, fw):
730 return list(h.itertools.izip_longest([], cs, fillvalue=fw))
727 return list(h.itertools.izip_longest([], cs, fillvalue=fw))
731
728
732 for c_type, raw_id in mark(commit_changes.added, 'a') \
729 for c_type, raw_id in mark(commit_changes.added, 'a') \
733 + mark(commit_changes.removed, 'r') \
730 + mark(commit_changes.removed, 'r') \
734 + mark(commit_changes.common, 'c'):
731 + mark(commit_changes.common, 'c'):
735
732
736 if raw_id in commit_cache:
733 if raw_id in commit_cache:
737 commit = commit_cache[raw_id]
734 commit = commit_cache[raw_id]
738 else:
735 else:
739 try:
736 try:
740 commit = commits_source_repo.get_commit(raw_id)
737 commit = commits_source_repo.get_commit(raw_id)
741 except CommitDoesNotExistError:
738 except CommitDoesNotExistError:
742 # in case we fail extracting still use "dummy" commit
739 # in case we fail extracting still use "dummy" commit
743 # for display in commit diff
740 # for display in commit diff
744 commit = h.AttributeDict(
741 commit = h.AttributeDict(
745 {'raw_id': raw_id,
742 {'raw_id': raw_id,
746 'message': 'EMPTY or MISSING COMMIT'})
743 'message': 'EMPTY or MISSING COMMIT'})
747 c.commit_changes.append([c_type, commit])
744 c.commit_changes.append([c_type, commit])
748
745
749 # current user review statuses for each version
746 # current user review statuses for each version
750 c.review_versions = {}
747 c.review_versions = {}
751 if self._rhodecode_user.user_id in c.allowed_reviewers:
748 if self._rhodecode_user.user_id in c.allowed_reviewers:
752 for co in general_comments:
749 for co in general_comments:
753 if co.author.user_id == self._rhodecode_user.user_id:
750 if co.author.user_id == self._rhodecode_user.user_id:
754 status = co.status_change
751 status = co.status_change
755 if status:
752 if status:
756 _ver_pr = status[0].comment.pull_request_version_id
753 _ver_pr = status[0].comment.pull_request_version_id
757 c.review_versions[_ver_pr] = status[0]
754 c.review_versions[_ver_pr] = status[0]
758
755
759 return self._get_template_context(c)
756 return self._get_template_context(c)
760
757
761 def get_commits(
758 def get_commits(
762 self, commits_source_repo, pull_request_at_ver, source_commit,
759 self, commits_source_repo, pull_request_at_ver, source_commit,
763 source_ref_id, source_scm, target_commit, target_ref_id, target_scm,
760 source_ref_id, source_scm, target_commit, target_ref_id, target_scm,
764 maybe_unreachable=False):
761 maybe_unreachable=False):
765
762
766 commit_cache = collections.OrderedDict()
763 commit_cache = collections.OrderedDict()
767 missing_requirements = False
764 missing_requirements = False
768
765
769 try:
766 try:
770 pre_load = ["author", "date", "message", "branch", "parents"]
767 pre_load = ["author", "date", "message", "branch", "parents"]
771
768
772 pull_request_commits = pull_request_at_ver.revisions
769 pull_request_commits = pull_request_at_ver.revisions
773 log.debug('Loading %s commits from %s',
770 log.debug('Loading %s commits from %s',
774 len(pull_request_commits), commits_source_repo)
771 len(pull_request_commits), commits_source_repo)
775
772
776 for rev in pull_request_commits:
773 for rev in pull_request_commits:
777 comm = commits_source_repo.get_commit(commit_id=rev, pre_load=pre_load,
774 comm = commits_source_repo.get_commit(commit_id=rev, pre_load=pre_load,
778 maybe_unreachable=maybe_unreachable)
775 maybe_unreachable=maybe_unreachable)
779 commit_cache[comm.raw_id] = comm
776 commit_cache[comm.raw_id] = comm
780
777
781 # Order here matters, we first need to get target, and then
778 # Order here matters, we first need to get target, and then
782 # the source
779 # the source
783 target_commit = commits_source_repo.get_commit(
780 target_commit = commits_source_repo.get_commit(
784 commit_id=safe_str(target_ref_id))
781 commit_id=safe_str(target_ref_id))
785
782
786 source_commit = commits_source_repo.get_commit(
783 source_commit = commits_source_repo.get_commit(
787 commit_id=safe_str(source_ref_id), maybe_unreachable=True)
784 commit_id=safe_str(source_ref_id), maybe_unreachable=True)
788 except CommitDoesNotExistError:
785 except CommitDoesNotExistError:
789 log.warning('Failed to get commit from `{}` repo'.format(
786 log.warning('Failed to get commit from `{}` repo'.format(
790 commits_source_repo), exc_info=True)
787 commits_source_repo), exc_info=True)
791 except RepositoryRequirementError:
788 except RepositoryRequirementError:
792 log.warning('Failed to get all required data from repo', exc_info=True)
789 log.warning('Failed to get all required data from repo', exc_info=True)
793 missing_requirements = True
790 missing_requirements = True
794
791
795 pr_ancestor_id = pull_request_at_ver.common_ancestor_id
792 pr_ancestor_id = pull_request_at_ver.common_ancestor_id
796
793
797 try:
794 try:
798 ancestor_commit = source_scm.get_commit(pr_ancestor_id)
795 ancestor_commit = source_scm.get_commit(pr_ancestor_id)
799 except Exception:
796 except Exception:
800 ancestor_commit = None
797 ancestor_commit = None
801
798
802 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
799 return ancestor_commit, commit_cache, missing_requirements, source_commit, target_commit
803
800
804 def assure_not_empty_repo(self):
801 def assure_not_empty_repo(self):
805 _ = self.request.translate
802 _ = self.request.translate
806
803
807 try:
804 try:
808 self.db_repo.scm_instance().get_commit()
805 self.db_repo.scm_instance().get_commit()
809 except EmptyRepositoryError:
806 except EmptyRepositoryError:
810 h.flash(h.literal(_('There are no commits yet')),
807 h.flash(h.literal(_('There are no commits yet')),
811 category='warning')
808 category='warning')
812 raise HTTPFound(
809 raise HTTPFound(
813 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
810 h.route_path('repo_summary', repo_name=self.db_repo.repo_name))
814
811
815 @LoginRequired()
812 @LoginRequired()
816 @NotAnonymous()
813 @NotAnonymous()
817 @HasRepoPermissionAnyDecorator(
814 @HasRepoPermissionAnyDecorator(
818 'repository.read', 'repository.write', 'repository.admin')
815 'repository.read', 'repository.write', 'repository.admin')
819 @view_config(
816 @view_config(
820 route_name='pullrequest_new', request_method='GET',
817 route_name='pullrequest_new', request_method='GET',
821 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
818 renderer='rhodecode:templates/pullrequests/pullrequest.mako')
822 def pull_request_new(self):
819 def pull_request_new(self):
823 _ = self.request.translate
820 _ = self.request.translate
824 c = self.load_default_context()
821 c = self.load_default_context()
825
822
826 self.assure_not_empty_repo()
823 self.assure_not_empty_repo()
827 source_repo = self.db_repo
824 source_repo = self.db_repo
828
825
829 commit_id = self.request.GET.get('commit')
826 commit_id = self.request.GET.get('commit')
830 branch_ref = self.request.GET.get('branch')
827 branch_ref = self.request.GET.get('branch')
831 bookmark_ref = self.request.GET.get('bookmark')
828 bookmark_ref = self.request.GET.get('bookmark')
832
829
833 try:
830 try:
834 source_repo_data = PullRequestModel().generate_repo_data(
831 source_repo_data = PullRequestModel().generate_repo_data(
835 source_repo, commit_id=commit_id,
832 source_repo, commit_id=commit_id,
836 branch=branch_ref, bookmark=bookmark_ref,
833 branch=branch_ref, bookmark=bookmark_ref,
837 translator=self.request.translate)
834 translator=self.request.translate)
838 except CommitDoesNotExistError as e:
835 except CommitDoesNotExistError as e:
839 log.exception(e)
836 log.exception(e)
840 h.flash(_('Commit does not exist'), 'error')
837 h.flash(_('Commit does not exist'), 'error')
841 raise HTTPFound(
838 raise HTTPFound(
842 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
839 h.route_path('pullrequest_new', repo_name=source_repo.repo_name))
843
840
844 default_target_repo = source_repo
841 default_target_repo = source_repo
845
842
846 if source_repo.parent and c.has_origin_repo_read_perm:
843 if source_repo.parent and c.has_origin_repo_read_perm:
847 parent_vcs_obj = source_repo.parent.scm_instance()
844 parent_vcs_obj = source_repo.parent.scm_instance()
848 if parent_vcs_obj and not parent_vcs_obj.is_empty():
845 if parent_vcs_obj and not parent_vcs_obj.is_empty():
849 # change default if we have a parent repo
846 # change default if we have a parent repo
850 default_target_repo = source_repo.parent
847 default_target_repo = source_repo.parent
851
848
852 target_repo_data = PullRequestModel().generate_repo_data(
849 target_repo_data = PullRequestModel().generate_repo_data(
853 default_target_repo, translator=self.request.translate)
850 default_target_repo, translator=self.request.translate)
854
851
855 selected_source_ref = source_repo_data['refs']['selected_ref']
852 selected_source_ref = source_repo_data['refs']['selected_ref']
856 title_source_ref = ''
853 title_source_ref = ''
857 if selected_source_ref:
854 if selected_source_ref:
858 title_source_ref = selected_source_ref.split(':', 2)[1]
855 title_source_ref = selected_source_ref.split(':', 2)[1]
859 c.default_title = PullRequestModel().generate_pullrequest_title(
856 c.default_title = PullRequestModel().generate_pullrequest_title(
860 source=source_repo.repo_name,
857 source=source_repo.repo_name,
861 source_ref=title_source_ref,
858 source_ref=title_source_ref,
862 target=default_target_repo.repo_name
859 target=default_target_repo.repo_name
863 )
860 )
864
861
865 c.default_repo_data = {
862 c.default_repo_data = {
866 'source_repo_name': source_repo.repo_name,
863 'source_repo_name': source_repo.repo_name,
867 'source_refs_json': json.dumps(source_repo_data),
864 'source_refs_json': json.dumps(source_repo_data),
868 'target_repo_name': default_target_repo.repo_name,
865 'target_repo_name': default_target_repo.repo_name,
869 'target_refs_json': json.dumps(target_repo_data),
866 'target_refs_json': json.dumps(target_repo_data),
870 }
867 }
871 c.default_source_ref = selected_source_ref
868 c.default_source_ref = selected_source_ref
872
869
873 return self._get_template_context(c)
870 return self._get_template_context(c)
874
871
875 @LoginRequired()
872 @LoginRequired()
876 @NotAnonymous()
873 @NotAnonymous()
877 @HasRepoPermissionAnyDecorator(
874 @HasRepoPermissionAnyDecorator(
878 'repository.read', 'repository.write', 'repository.admin')
875 'repository.read', 'repository.write', 'repository.admin')
879 @view_config(
876 @view_config(
880 route_name='pullrequest_repo_refs', request_method='GET',
877 route_name='pullrequest_repo_refs', request_method='GET',
881 renderer='json_ext', xhr=True)
878 renderer='json_ext', xhr=True)
882 def pull_request_repo_refs(self):
879 def pull_request_repo_refs(self):
883 self.load_default_context()
880 self.load_default_context()
884 target_repo_name = self.request.matchdict['target_repo_name']
881 target_repo_name = self.request.matchdict['target_repo_name']
885 repo = Repository.get_by_repo_name(target_repo_name)
882 repo = Repository.get_by_repo_name(target_repo_name)
886 if not repo:
883 if not repo:
887 raise HTTPNotFound()
884 raise HTTPNotFound()
888
885
889 target_perm = HasRepoPermissionAny(
886 target_perm = HasRepoPermissionAny(
890 'repository.read', 'repository.write', 'repository.admin')(
887 'repository.read', 'repository.write', 'repository.admin')(
891 target_repo_name)
888 target_repo_name)
892 if not target_perm:
889 if not target_perm:
893 raise HTTPNotFound()
890 raise HTTPNotFound()
894
891
895 return PullRequestModel().generate_repo_data(
892 return PullRequestModel().generate_repo_data(
896 repo, translator=self.request.translate)
893 repo, translator=self.request.translate)
897
894
898 @LoginRequired()
895 @LoginRequired()
899 @NotAnonymous()
896 @NotAnonymous()
900 @HasRepoPermissionAnyDecorator(
897 @HasRepoPermissionAnyDecorator(
901 'repository.read', 'repository.write', 'repository.admin')
898 'repository.read', 'repository.write', 'repository.admin')
902 @view_config(
899 @view_config(
903 route_name='pullrequest_repo_targets', request_method='GET',
900 route_name='pullrequest_repo_targets', request_method='GET',
904 renderer='json_ext', xhr=True)
901 renderer='json_ext', xhr=True)
905 def pullrequest_repo_targets(self):
902 def pullrequest_repo_targets(self):
906 _ = self.request.translate
903 _ = self.request.translate
907 filter_query = self.request.GET.get('query')
904 filter_query = self.request.GET.get('query')
908
905
909 # get the parents
906 # get the parents
910 parent_target_repos = []
907 parent_target_repos = []
911 if self.db_repo.parent:
908 if self.db_repo.parent:
912 parents_query = Repository.query() \
909 parents_query = Repository.query() \
913 .order_by(func.length(Repository.repo_name)) \
910 .order_by(func.length(Repository.repo_name)) \
914 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
911 .filter(Repository.fork_id == self.db_repo.parent.repo_id)
915
912
916 if filter_query:
913 if filter_query:
917 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
914 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
918 parents_query = parents_query.filter(
915 parents_query = parents_query.filter(
919 Repository.repo_name.ilike(ilike_expression))
916 Repository.repo_name.ilike(ilike_expression))
920 parents = parents_query.limit(20).all()
917 parents = parents_query.limit(20).all()
921
918
922 for parent in parents:
919 for parent in parents:
923 parent_vcs_obj = parent.scm_instance()
920 parent_vcs_obj = parent.scm_instance()
924 if parent_vcs_obj and not parent_vcs_obj.is_empty():
921 if parent_vcs_obj and not parent_vcs_obj.is_empty():
925 parent_target_repos.append(parent)
922 parent_target_repos.append(parent)
926
923
927 # get other forks, and repo itself
924 # get other forks, and repo itself
928 query = Repository.query() \
925 query = Repository.query() \
929 .order_by(func.length(Repository.repo_name)) \
926 .order_by(func.length(Repository.repo_name)) \
930 .filter(
927 .filter(
931 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
928 or_(Repository.repo_id == self.db_repo.repo_id, # repo itself
932 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
929 Repository.fork_id == self.db_repo.repo_id) # forks of this repo
933 ) \
930 ) \
934 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
931 .filter(~Repository.repo_id.in_([x.repo_id for x in parent_target_repos]))
935
932
936 if filter_query:
933 if filter_query:
937 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
934 ilike_expression = u'%{}%'.format(safe_unicode(filter_query))
938 query = query.filter(Repository.repo_name.ilike(ilike_expression))
935 query = query.filter(Repository.repo_name.ilike(ilike_expression))
939
936
940 limit = max(20 - len(parent_target_repos), 5) # not less then 5
937 limit = max(20 - len(parent_target_repos), 5) # not less then 5
941 target_repos = query.limit(limit).all()
938 target_repos = query.limit(limit).all()
942
939
943 all_target_repos = target_repos + parent_target_repos
940 all_target_repos = target_repos + parent_target_repos
944
941
945 repos = []
942 repos = []
946 # This checks permissions to the repositories
943 # This checks permissions to the repositories
947 for obj in ScmModel().get_repos(all_target_repos):
944 for obj in ScmModel().get_repos(all_target_repos):
948 repos.append({
945 repos.append({
949 'id': obj['name'],
946 'id': obj['name'],
950 'text': obj['name'],
947 'text': obj['name'],
951 'type': 'repo',
948 'type': 'repo',
952 'repo_id': obj['dbrepo']['repo_id'],
949 'repo_id': obj['dbrepo']['repo_id'],
953 'repo_type': obj['dbrepo']['repo_type'],
950 'repo_type': obj['dbrepo']['repo_type'],
954 'private': obj['dbrepo']['private'],
951 'private': obj['dbrepo']['private'],
955
952
956 })
953 })
957
954
958 data = {
955 data = {
959 'more': False,
956 'more': False,
960 'results': [{
957 'results': [{
961 'text': _('Repositories'),
958 'text': _('Repositories'),
962 'children': repos
959 'children': repos
963 }] if repos else []
960 }] if repos else []
964 }
961 }
965 return data
962 return data
966
963
967 @LoginRequired()
964 @LoginRequired()
968 @NotAnonymous()
965 @NotAnonymous()
969 @HasRepoPermissionAnyDecorator(
966 @HasRepoPermissionAnyDecorator(
970 'repository.read', 'repository.write', 'repository.admin')
967 'repository.read', 'repository.write', 'repository.admin')
971 @view_config(
968 @view_config(
972 route_name='pullrequest_comments', request_method='POST',
969 route_name='pullrequest_comments', request_method='POST',
973 renderer='string', xhr=True)
970 renderer='string', xhr=True)
974 def pullrequest_comments(self):
971 def pullrequest_comments(self):
975 self.load_default_context()
972 self.load_default_context()
976
973
977 pull_request = PullRequest.get_or_404(
974 pull_request = PullRequest.get_or_404(
978 self.request.matchdict['pull_request_id'])
975 self.request.matchdict['pull_request_id'])
979 pull_request_id = pull_request.pull_request_id
976 pull_request_id = pull_request.pull_request_id
980 version = self.request.GET.get('version')
977 version = self.request.GET.get('version')
981
978
982 _render = self.request.get_partial_renderer(
979 _render = self.request.get_partial_renderer(
983 'rhodecode:templates/pullrequests/pullrequest_show.mako')
980 'rhodecode:templates/base/sidebar.mako')
984 c = _render.get_call_context()
981 c = _render.get_call_context()
985
982
986 (pull_request_latest,
983 (pull_request_latest,
987 pull_request_at_ver,
984 pull_request_at_ver,
988 pull_request_display_obj,
985 pull_request_display_obj,
989 at_version) = PullRequestModel().get_pr_version(
986 at_version) = PullRequestModel().get_pr_version(
990 pull_request_id, version=version)
987 pull_request_id, version=version)
991 versions = pull_request_display_obj.versions()
988 versions = pull_request_display_obj.versions()
992 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
989 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
993 c.versions = versions + [latest_ver]
990 c.versions = versions + [latest_ver]
994
991
995 c.at_version = at_version
992 c.at_version = at_version
996 c.at_version_num = (at_version
993 c.at_version_num = (at_version
997 if at_version and at_version != PullRequest.LATEST_VER
994 if at_version and at_version != PullRequest.LATEST_VER
998 else None)
995 else None)
999
996
1000 self.register_comments_vars(c, pull_request_latest, versions)
997 self.register_comments_vars(c, pull_request_latest, versions)
1001 all_comments = c.inline_comments_flat + c.comments
998 all_comments = c.inline_comments_flat + c.comments
1002 return _render('comments_table', all_comments, len(all_comments))
999
1000 existing_ids = filter(
1001 lambda e: e, map(safe_int, self.request.POST.getall('comments[]')))
1002 return _render('comments_table', all_comments, len(all_comments),
1003 existing_ids=existing_ids)
1003
1004
1004 @LoginRequired()
1005 @LoginRequired()
1005 @NotAnonymous()
1006 @NotAnonymous()
1006 @HasRepoPermissionAnyDecorator(
1007 @HasRepoPermissionAnyDecorator(
1007 'repository.read', 'repository.write', 'repository.admin')
1008 'repository.read', 'repository.write', 'repository.admin')
1008 @view_config(
1009 @view_config(
1009 route_name='pullrequest_todos', request_method='POST',
1010 route_name='pullrequest_todos', request_method='POST',
1010 renderer='string', xhr=True)
1011 renderer='string', xhr=True)
1011 def pullrequest_todos(self):
1012 def pullrequest_todos(self):
1012 self.load_default_context()
1013 self.load_default_context()
1013
1014
1014 pull_request = PullRequest.get_or_404(
1015 pull_request = PullRequest.get_or_404(
1015 self.request.matchdict['pull_request_id'])
1016 self.request.matchdict['pull_request_id'])
1016 pull_request_id = pull_request.pull_request_id
1017 pull_request_id = pull_request.pull_request_id
1017 version = self.request.GET.get('version')
1018 version = self.request.GET.get('version')
1018
1019
1019 _render = self.request.get_partial_renderer(
1020 _render = self.request.get_partial_renderer(
1020 'rhodecode:templates/pullrequests/pullrequest_show.mako')
1021 'rhodecode:templates/base/sidebar.mako')
1021 c = _render.get_call_context()
1022 c = _render.get_call_context()
1022 (pull_request_latest,
1023 (pull_request_latest,
1023 pull_request_at_ver,
1024 pull_request_at_ver,
1024 pull_request_display_obj,
1025 pull_request_display_obj,
1025 at_version) = PullRequestModel().get_pr_version(
1026 at_version) = PullRequestModel().get_pr_version(
1026 pull_request_id, version=version)
1027 pull_request_id, version=version)
1027 versions = pull_request_display_obj.versions()
1028 versions = pull_request_display_obj.versions()
1028 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1029 latest_ver = PullRequest.get_pr_display_object(pull_request_latest, pull_request_latest)
1029 c.versions = versions + [latest_ver]
1030 c.versions = versions + [latest_ver]
1030
1031
1031 c.at_version = at_version
1032 c.at_version = at_version
1032 c.at_version_num = (at_version
1033 c.at_version_num = (at_version
1033 if at_version and at_version != PullRequest.LATEST_VER
1034 if at_version and at_version != PullRequest.LATEST_VER
1034 else None)
1035 else None)
1035
1036
1036 c.unresolved_comments = CommentsModel() \
1037 c.unresolved_comments = CommentsModel() \
1037 .get_pull_request_unresolved_todos(pull_request)
1038 .get_pull_request_unresolved_todos(pull_request)
1038 c.resolved_comments = CommentsModel() \
1039 c.resolved_comments = CommentsModel() \
1039 .get_pull_request_resolved_todos(pull_request)
1040 .get_pull_request_resolved_todos(pull_request)
1040
1041
1041 all_comments = c.unresolved_comments + c.resolved_comments
1042 all_comments = c.unresolved_comments + c.resolved_comments
1042 return _render('comments_table', all_comments, len(c.unresolved_comments), todo_comments=True)
1043 existing_ids = filter(
1044 lambda e: e, map(safe_int, self.request.POST.getall('comments[]')))
1045 return _render('comments_table', all_comments, len(c.unresolved_comments),
1046 todo_comments=True, existing_ids=existing_ids)
1043
1047
1044 @LoginRequired()
1048 @LoginRequired()
1045 @NotAnonymous()
1049 @NotAnonymous()
1046 @HasRepoPermissionAnyDecorator(
1050 @HasRepoPermissionAnyDecorator(
1047 'repository.read', 'repository.write', 'repository.admin')
1051 'repository.read', 'repository.write', 'repository.admin')
1048 @CSRFRequired()
1052 @CSRFRequired()
1049 @view_config(
1053 @view_config(
1050 route_name='pullrequest_create', request_method='POST',
1054 route_name='pullrequest_create', request_method='POST',
1051 renderer=None)
1055 renderer=None)
1052 def pull_request_create(self):
1056 def pull_request_create(self):
1053 _ = self.request.translate
1057 _ = self.request.translate
1054 self.assure_not_empty_repo()
1058 self.assure_not_empty_repo()
1055 self.load_default_context()
1059 self.load_default_context()
1056
1060
1057 controls = peppercorn.parse(self.request.POST.items())
1061 controls = peppercorn.parse(self.request.POST.items())
1058
1062
1059 try:
1063 try:
1060 form = PullRequestForm(
1064 form = PullRequestForm(
1061 self.request.translate, self.db_repo.repo_id)()
1065 self.request.translate, self.db_repo.repo_id)()
1062 _form = form.to_python(controls)
1066 _form = form.to_python(controls)
1063 except formencode.Invalid as errors:
1067 except formencode.Invalid as errors:
1064 if errors.error_dict.get('revisions'):
1068 if errors.error_dict.get('revisions'):
1065 msg = 'Revisions: %s' % errors.error_dict['revisions']
1069 msg = 'Revisions: %s' % errors.error_dict['revisions']
1066 elif errors.error_dict.get('pullrequest_title'):
1070 elif errors.error_dict.get('pullrequest_title'):
1067 msg = errors.error_dict.get('pullrequest_title')
1071 msg = errors.error_dict.get('pullrequest_title')
1068 else:
1072 else:
1069 msg = _('Error creating pull request: {}').format(errors)
1073 msg = _('Error creating pull request: {}').format(errors)
1070 log.exception(msg)
1074 log.exception(msg)
1071 h.flash(msg, 'error')
1075 h.flash(msg, 'error')
1072
1076
1073 # would rather just go back to form ...
1077 # would rather just go back to form ...
1074 raise HTTPFound(
1078 raise HTTPFound(
1075 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1079 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1076
1080
1077 source_repo = _form['source_repo']
1081 source_repo = _form['source_repo']
1078 source_ref = _form['source_ref']
1082 source_ref = _form['source_ref']
1079 target_repo = _form['target_repo']
1083 target_repo = _form['target_repo']
1080 target_ref = _form['target_ref']
1084 target_ref = _form['target_ref']
1081 commit_ids = _form['revisions'][::-1]
1085 commit_ids = _form['revisions'][::-1]
1082 common_ancestor_id = _form['common_ancestor']
1086 common_ancestor_id = _form['common_ancestor']
1083
1087
1084 # find the ancestor for this pr
1088 # find the ancestor for this pr
1085 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
1089 source_db_repo = Repository.get_by_repo_name(_form['source_repo'])
1086 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
1090 target_db_repo = Repository.get_by_repo_name(_form['target_repo'])
1087
1091
1088 if not (source_db_repo or target_db_repo):
1092 if not (source_db_repo or target_db_repo):
1089 h.flash(_('source_repo or target repo not found'), category='error')
1093 h.flash(_('source_repo or target repo not found'), category='error')
1090 raise HTTPFound(
1094 raise HTTPFound(
1091 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1095 h.route_path('pullrequest_new', repo_name=self.db_repo_name))
1092
1096
1093 # re-check permissions again here
1097 # re-check permissions again here
1094 # source_repo we must have read permissions
1098 # source_repo we must have read permissions
1095
1099
1096 source_perm = HasRepoPermissionAny(
1100 source_perm = HasRepoPermissionAny(
1097 'repository.read', 'repository.write', 'repository.admin')(
1101 'repository.read', 'repository.write', 'repository.admin')(
1098 source_db_repo.repo_name)
1102 source_db_repo.repo_name)
1099 if not source_perm:
1103 if not source_perm:
1100 msg = _('Not Enough permissions to source repo `{}`.'.format(
1104 msg = _('Not Enough permissions to source repo `{}`.'.format(
1101 source_db_repo.repo_name))
1105 source_db_repo.repo_name))
1102 h.flash(msg, category='error')
1106 h.flash(msg, category='error')
1103 # copy the args back to redirect
1107 # copy the args back to redirect
1104 org_query = self.request.GET.mixed()
1108 org_query = self.request.GET.mixed()
1105 raise HTTPFound(
1109 raise HTTPFound(
1106 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1110 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1107 _query=org_query))
1111 _query=org_query))
1108
1112
1109 # target repo we must have read permissions, and also later on
1113 # target repo we must have read permissions, and also later on
1110 # we want to check branch permissions here
1114 # we want to check branch permissions here
1111 target_perm = HasRepoPermissionAny(
1115 target_perm = HasRepoPermissionAny(
1112 'repository.read', 'repository.write', 'repository.admin')(
1116 'repository.read', 'repository.write', 'repository.admin')(
1113 target_db_repo.repo_name)
1117 target_db_repo.repo_name)
1114 if not target_perm:
1118 if not target_perm:
1115 msg = _('Not Enough permissions to target repo `{}`.'.format(
1119 msg = _('Not Enough permissions to target repo `{}`.'.format(
1116 target_db_repo.repo_name))
1120 target_db_repo.repo_name))
1117 h.flash(msg, category='error')
1121 h.flash(msg, category='error')
1118 # copy the args back to redirect
1122 # copy the args back to redirect
1119 org_query = self.request.GET.mixed()
1123 org_query = self.request.GET.mixed()
1120 raise HTTPFound(
1124 raise HTTPFound(
1121 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1125 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1122 _query=org_query))
1126 _query=org_query))
1123
1127
1124 source_scm = source_db_repo.scm_instance()
1128 source_scm = source_db_repo.scm_instance()
1125 target_scm = target_db_repo.scm_instance()
1129 target_scm = target_db_repo.scm_instance()
1126
1130
1127 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
1131 source_commit = source_scm.get_commit(source_ref.split(':')[-1])
1128 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
1132 target_commit = target_scm.get_commit(target_ref.split(':')[-1])
1129
1133
1130 ancestor = source_scm.get_common_ancestor(
1134 ancestor = source_scm.get_common_ancestor(
1131 source_commit.raw_id, target_commit.raw_id, target_scm)
1135 source_commit.raw_id, target_commit.raw_id, target_scm)
1132
1136
1133 # recalculate target ref based on ancestor
1137 # recalculate target ref based on ancestor
1134 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
1138 target_ref_type, target_ref_name, __ = _form['target_ref'].split(':')
1135 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
1139 target_ref = ':'.join((target_ref_type, target_ref_name, ancestor))
1136
1140
1137 get_default_reviewers_data, validate_default_reviewers = \
1141 get_default_reviewers_data, validate_default_reviewers = \
1138 PullRequestModel().get_reviewer_functions()
1142 PullRequestModel().get_reviewer_functions()
1139
1143
1140 # recalculate reviewers logic, to make sure we can validate this
1144 # recalculate reviewers logic, to make sure we can validate this
1141 reviewer_rules = get_default_reviewers_data(
1145 reviewer_rules = get_default_reviewers_data(
1142 self._rhodecode_db_user, source_db_repo,
1146 self._rhodecode_db_user, source_db_repo,
1143 source_commit, target_db_repo, target_commit)
1147 source_commit, target_db_repo, target_commit)
1144
1148
1145 given_reviewers = _form['review_members']
1149 given_reviewers = _form['review_members']
1146 reviewers = validate_default_reviewers(
1150 reviewers = validate_default_reviewers(
1147 given_reviewers, reviewer_rules)
1151 given_reviewers, reviewer_rules)
1148
1152
1149 pullrequest_title = _form['pullrequest_title']
1153 pullrequest_title = _form['pullrequest_title']
1150 title_source_ref = source_ref.split(':', 2)[1]
1154 title_source_ref = source_ref.split(':', 2)[1]
1151 if not pullrequest_title:
1155 if not pullrequest_title:
1152 pullrequest_title = PullRequestModel().generate_pullrequest_title(
1156 pullrequest_title = PullRequestModel().generate_pullrequest_title(
1153 source=source_repo,
1157 source=source_repo,
1154 source_ref=title_source_ref,
1158 source_ref=title_source_ref,
1155 target=target_repo
1159 target=target_repo
1156 )
1160 )
1157
1161
1158 description = _form['pullrequest_desc']
1162 description = _form['pullrequest_desc']
1159 description_renderer = _form['description_renderer']
1163 description_renderer = _form['description_renderer']
1160
1164
1161 try:
1165 try:
1162 pull_request = PullRequestModel().create(
1166 pull_request = PullRequestModel().create(
1163 created_by=self._rhodecode_user.user_id,
1167 created_by=self._rhodecode_user.user_id,
1164 source_repo=source_repo,
1168 source_repo=source_repo,
1165 source_ref=source_ref,
1169 source_ref=source_ref,
1166 target_repo=target_repo,
1170 target_repo=target_repo,
1167 target_ref=target_ref,
1171 target_ref=target_ref,
1168 revisions=commit_ids,
1172 revisions=commit_ids,
1169 common_ancestor_id=common_ancestor_id,
1173 common_ancestor_id=common_ancestor_id,
1170 reviewers=reviewers,
1174 reviewers=reviewers,
1171 title=pullrequest_title,
1175 title=pullrequest_title,
1172 description=description,
1176 description=description,
1173 description_renderer=description_renderer,
1177 description_renderer=description_renderer,
1174 reviewer_data=reviewer_rules,
1178 reviewer_data=reviewer_rules,
1175 auth_user=self._rhodecode_user
1179 auth_user=self._rhodecode_user
1176 )
1180 )
1177 Session().commit()
1181 Session().commit()
1178
1182
1179 h.flash(_('Successfully opened new pull request'),
1183 h.flash(_('Successfully opened new pull request'),
1180 category='success')
1184 category='success')
1181 except Exception:
1185 except Exception:
1182 msg = _('Error occurred during creation of this pull request.')
1186 msg = _('Error occurred during creation of this pull request.')
1183 log.exception(msg)
1187 log.exception(msg)
1184 h.flash(msg, category='error')
1188 h.flash(msg, category='error')
1185
1189
1186 # copy the args back to redirect
1190 # copy the args back to redirect
1187 org_query = self.request.GET.mixed()
1191 org_query = self.request.GET.mixed()
1188 raise HTTPFound(
1192 raise HTTPFound(
1189 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1193 h.route_path('pullrequest_new', repo_name=self.db_repo_name,
1190 _query=org_query))
1194 _query=org_query))
1191
1195
1192 raise HTTPFound(
1196 raise HTTPFound(
1193 h.route_path('pullrequest_show', repo_name=target_repo,
1197 h.route_path('pullrequest_show', repo_name=target_repo,
1194 pull_request_id=pull_request.pull_request_id))
1198 pull_request_id=pull_request.pull_request_id))
1195
1199
1196 @LoginRequired()
1200 @LoginRequired()
1197 @NotAnonymous()
1201 @NotAnonymous()
1198 @HasRepoPermissionAnyDecorator(
1202 @HasRepoPermissionAnyDecorator(
1199 'repository.read', 'repository.write', 'repository.admin')
1203 'repository.read', 'repository.write', 'repository.admin')
1200 @CSRFRequired()
1204 @CSRFRequired()
1201 @view_config(
1205 @view_config(
1202 route_name='pullrequest_update', request_method='POST',
1206 route_name='pullrequest_update', request_method='POST',
1203 renderer='json_ext')
1207 renderer='json_ext')
1204 def pull_request_update(self):
1208 def pull_request_update(self):
1205 pull_request = PullRequest.get_or_404(
1209 pull_request = PullRequest.get_or_404(
1206 self.request.matchdict['pull_request_id'])
1210 self.request.matchdict['pull_request_id'])
1207 _ = self.request.translate
1211 _ = self.request.translate
1208
1212
1209 c = self.load_default_context()
1213 c = self.load_default_context()
1210 redirect_url = None
1214 redirect_url = None
1211
1215
1212 if pull_request.is_closed():
1216 if pull_request.is_closed():
1213 log.debug('update: forbidden because pull request is closed')
1217 log.debug('update: forbidden because pull request is closed')
1214 msg = _(u'Cannot update closed pull requests.')
1218 msg = _(u'Cannot update closed pull requests.')
1215 h.flash(msg, category='error')
1219 h.flash(msg, category='error')
1216 return {'response': True,
1220 return {'response': True,
1217 'redirect_url': redirect_url}
1221 'redirect_url': redirect_url}
1218
1222
1219 is_state_changing = pull_request.is_state_changing()
1223 is_state_changing = pull_request.is_state_changing()
1220 c.pr_broadcast_channel = '/repo${}$/pr/{}'.format(
1224 c.pr_broadcast_channel = '/repo${}$/pr/{}'.format(
1221 pull_request.target_repo.repo_name, pull_request.pull_request_id)
1225 pull_request.target_repo.repo_name, pull_request.pull_request_id)
1222
1226
1223 # only owner or admin can update it
1227 # only owner or admin can update it
1224 allowed_to_update = PullRequestModel().check_user_update(
1228 allowed_to_update = PullRequestModel().check_user_update(
1225 pull_request, self._rhodecode_user)
1229 pull_request, self._rhodecode_user)
1226 if allowed_to_update:
1230 if allowed_to_update:
1227 controls = peppercorn.parse(self.request.POST.items())
1231 controls = peppercorn.parse(self.request.POST.items())
1228 force_refresh = str2bool(self.request.POST.get('force_refresh'))
1232 force_refresh = str2bool(self.request.POST.get('force_refresh'))
1229
1233
1230 if 'review_members' in controls:
1234 if 'review_members' in controls:
1231 self._update_reviewers(
1235 self._update_reviewers(
1232 pull_request, controls['review_members'],
1236 pull_request, controls['review_members'],
1233 pull_request.reviewer_data)
1237 pull_request.reviewer_data)
1234 elif str2bool(self.request.POST.get('update_commits', 'false')):
1238 elif str2bool(self.request.POST.get('update_commits', 'false')):
1235 if is_state_changing:
1239 if is_state_changing:
1236 log.debug('commits update: forbidden because pull request is in state %s',
1240 log.debug('commits update: forbidden because pull request is in state %s',
1237 pull_request.pull_request_state)
1241 pull_request.pull_request_state)
1238 msg = _(u'Cannot update pull requests commits in state other than `{}`. '
1242 msg = _(u'Cannot update pull requests commits in state other than `{}`. '
1239 u'Current state is: `{}`').format(
1243 u'Current state is: `{}`').format(
1240 PullRequest.STATE_CREATED, pull_request.pull_request_state)
1244 PullRequest.STATE_CREATED, pull_request.pull_request_state)
1241 h.flash(msg, category='error')
1245 h.flash(msg, category='error')
1242 return {'response': True,
1246 return {'response': True,
1243 'redirect_url': redirect_url}
1247 'redirect_url': redirect_url}
1244
1248
1245 self._update_commits(c, pull_request)
1249 self._update_commits(c, pull_request)
1246 if force_refresh:
1250 if force_refresh:
1247 redirect_url = h.route_path(
1251 redirect_url = h.route_path(
1248 'pullrequest_show', repo_name=self.db_repo_name,
1252 'pullrequest_show', repo_name=self.db_repo_name,
1249 pull_request_id=pull_request.pull_request_id,
1253 pull_request_id=pull_request.pull_request_id,
1250 _query={"force_refresh": 1})
1254 _query={"force_refresh": 1})
1251 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1255 elif str2bool(self.request.POST.get('edit_pull_request', 'false')):
1252 self._edit_pull_request(pull_request)
1256 self._edit_pull_request(pull_request)
1253 else:
1257 else:
1254 raise HTTPBadRequest()
1258 raise HTTPBadRequest()
1255
1259
1256 return {'response': True,
1260 return {'response': True,
1257 'redirect_url': redirect_url}
1261 'redirect_url': redirect_url}
1258 raise HTTPForbidden()
1262 raise HTTPForbidden()
1259
1263
1260 def _edit_pull_request(self, pull_request):
1264 def _edit_pull_request(self, pull_request):
1261 _ = self.request.translate
1265 _ = self.request.translate
1262
1266
1263 try:
1267 try:
1264 PullRequestModel().edit(
1268 PullRequestModel().edit(
1265 pull_request,
1269 pull_request,
1266 self.request.POST.get('title'),
1270 self.request.POST.get('title'),
1267 self.request.POST.get('description'),
1271 self.request.POST.get('description'),
1268 self.request.POST.get('description_renderer'),
1272 self.request.POST.get('description_renderer'),
1269 self._rhodecode_user)
1273 self._rhodecode_user)
1270 except ValueError:
1274 except ValueError:
1271 msg = _(u'Cannot update closed pull requests.')
1275 msg = _(u'Cannot update closed pull requests.')
1272 h.flash(msg, category='error')
1276 h.flash(msg, category='error')
1273 return
1277 return
1274 else:
1278 else:
1275 Session().commit()
1279 Session().commit()
1276
1280
1277 msg = _(u'Pull request title & description updated.')
1281 msg = _(u'Pull request title & description updated.')
1278 h.flash(msg, category='success')
1282 h.flash(msg, category='success')
1279 return
1283 return
1280
1284
1281 def _update_commits(self, c, pull_request):
1285 def _update_commits(self, c, pull_request):
1282 _ = self.request.translate
1286 _ = self.request.translate
1283
1287
1284 with pull_request.set_state(PullRequest.STATE_UPDATING):
1288 with pull_request.set_state(PullRequest.STATE_UPDATING):
1285 resp = PullRequestModel().update_commits(
1289 resp = PullRequestModel().update_commits(
1286 pull_request, self._rhodecode_db_user)
1290 pull_request, self._rhodecode_db_user)
1287
1291
1288 if resp.executed:
1292 if resp.executed:
1289
1293
1290 if resp.target_changed and resp.source_changed:
1294 if resp.target_changed and resp.source_changed:
1291 changed = 'target and source repositories'
1295 changed = 'target and source repositories'
1292 elif resp.target_changed and not resp.source_changed:
1296 elif resp.target_changed and not resp.source_changed:
1293 changed = 'target repository'
1297 changed = 'target repository'
1294 elif not resp.target_changed and resp.source_changed:
1298 elif not resp.target_changed and resp.source_changed:
1295 changed = 'source repository'
1299 changed = 'source repository'
1296 else:
1300 else:
1297 changed = 'nothing'
1301 changed = 'nothing'
1298
1302
1299 msg = _(u'Pull request updated to "{source_commit_id}" with '
1303 msg = _(u'Pull request updated to "{source_commit_id}" with '
1300 u'{count_added} added, {count_removed} removed commits. '
1304 u'{count_added} added, {count_removed} removed commits. '
1301 u'Source of changes: {change_source}')
1305 u'Source of changes: {change_source}')
1302 msg = msg.format(
1306 msg = msg.format(
1303 source_commit_id=pull_request.source_ref_parts.commit_id,
1307 source_commit_id=pull_request.source_ref_parts.commit_id,
1304 count_added=len(resp.changes.added),
1308 count_added=len(resp.changes.added),
1305 count_removed=len(resp.changes.removed),
1309 count_removed=len(resp.changes.removed),
1306 change_source=changed)
1310 change_source=changed)
1307 h.flash(msg, category='success')
1311 h.flash(msg, category='success')
1308
1312
1309 message = msg + (
1313 message = msg + (
1310 ' - <a onclick="window.location.reload()">'
1314 ' - <a onclick="window.location.reload()">'
1311 '<strong>{}</strong></a>'.format(_('Reload page')))
1315 '<strong>{}</strong></a>'.format(_('Reload page')))
1312
1316
1313 message_obj = {
1317 message_obj = {
1314 'message': message,
1318 'message': message,
1315 'level': 'success',
1319 'level': 'success',
1316 'topic': '/notifications'
1320 'topic': '/notifications'
1317 }
1321 }
1318
1322
1319 channelstream.post_message(
1323 channelstream.post_message(
1320 c.pr_broadcast_channel, message_obj, self._rhodecode_user.username,
1324 c.pr_broadcast_channel, message_obj, self._rhodecode_user.username,
1321 registry=self.request.registry)
1325 registry=self.request.registry)
1322 else:
1326 else:
1323 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1327 msg = PullRequestModel.UPDATE_STATUS_MESSAGES[resp.reason]
1324 warning_reasons = [
1328 warning_reasons = [
1325 UpdateFailureReason.NO_CHANGE,
1329 UpdateFailureReason.NO_CHANGE,
1326 UpdateFailureReason.WRONG_REF_TYPE,
1330 UpdateFailureReason.WRONG_REF_TYPE,
1327 ]
1331 ]
1328 category = 'warning' if resp.reason in warning_reasons else 'error'
1332 category = 'warning' if resp.reason in warning_reasons else 'error'
1329 h.flash(msg, category=category)
1333 h.flash(msg, category=category)
1330
1334
1331 @LoginRequired()
1335 @LoginRequired()
1332 @NotAnonymous()
1336 @NotAnonymous()
1333 @HasRepoPermissionAnyDecorator(
1337 @HasRepoPermissionAnyDecorator(
1334 'repository.read', 'repository.write', 'repository.admin')
1338 'repository.read', 'repository.write', 'repository.admin')
1335 @CSRFRequired()
1339 @CSRFRequired()
1336 @view_config(
1340 @view_config(
1337 route_name='pullrequest_merge', request_method='POST',
1341 route_name='pullrequest_merge', request_method='POST',
1338 renderer='json_ext')
1342 renderer='json_ext')
1339 def pull_request_merge(self):
1343 def pull_request_merge(self):
1340 """
1344 """
1341 Merge will perform a server-side merge of the specified
1345 Merge will perform a server-side merge of the specified
1342 pull request, if the pull request is approved and mergeable.
1346 pull request, if the pull request is approved and mergeable.
1343 After successful merging, the pull request is automatically
1347 After successful merging, the pull request is automatically
1344 closed, with a relevant comment.
1348 closed, with a relevant comment.
1345 """
1349 """
1346 pull_request = PullRequest.get_or_404(
1350 pull_request = PullRequest.get_or_404(
1347 self.request.matchdict['pull_request_id'])
1351 self.request.matchdict['pull_request_id'])
1348 _ = self.request.translate
1352 _ = self.request.translate
1349
1353
1350 if pull_request.is_state_changing():
1354 if pull_request.is_state_changing():
1351 log.debug('show: forbidden because pull request is in state %s',
1355 log.debug('show: forbidden because pull request is in state %s',
1352 pull_request.pull_request_state)
1356 pull_request.pull_request_state)
1353 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1357 msg = _(u'Cannot merge pull requests in state other than `{}`. '
1354 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1358 u'Current state is: `{}`').format(PullRequest.STATE_CREATED,
1355 pull_request.pull_request_state)
1359 pull_request.pull_request_state)
1356 h.flash(msg, category='error')
1360 h.flash(msg, category='error')
1357 raise HTTPFound(
1361 raise HTTPFound(
1358 h.route_path('pullrequest_show',
1362 h.route_path('pullrequest_show',
1359 repo_name=pull_request.target_repo.repo_name,
1363 repo_name=pull_request.target_repo.repo_name,
1360 pull_request_id=pull_request.pull_request_id))
1364 pull_request_id=pull_request.pull_request_id))
1361
1365
1362 self.load_default_context()
1366 self.load_default_context()
1363
1367
1364 with pull_request.set_state(PullRequest.STATE_UPDATING):
1368 with pull_request.set_state(PullRequest.STATE_UPDATING):
1365 check = MergeCheck.validate(
1369 check = MergeCheck.validate(
1366 pull_request, auth_user=self._rhodecode_user,
1370 pull_request, auth_user=self._rhodecode_user,
1367 translator=self.request.translate)
1371 translator=self.request.translate)
1368 merge_possible = not check.failed
1372 merge_possible = not check.failed
1369
1373
1370 for err_type, error_msg in check.errors:
1374 for err_type, error_msg in check.errors:
1371 h.flash(error_msg, category=err_type)
1375 h.flash(error_msg, category=err_type)
1372
1376
1373 if merge_possible:
1377 if merge_possible:
1374 log.debug("Pre-conditions checked, trying to merge.")
1378 log.debug("Pre-conditions checked, trying to merge.")
1375 extras = vcs_operation_context(
1379 extras = vcs_operation_context(
1376 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1380 self.request.environ, repo_name=pull_request.target_repo.repo_name,
1377 username=self._rhodecode_db_user.username, action='push',
1381 username=self._rhodecode_db_user.username, action='push',
1378 scm=pull_request.target_repo.repo_type)
1382 scm=pull_request.target_repo.repo_type)
1379 with pull_request.set_state(PullRequest.STATE_UPDATING):
1383 with pull_request.set_state(PullRequest.STATE_UPDATING):
1380 self._merge_pull_request(
1384 self._merge_pull_request(
1381 pull_request, self._rhodecode_db_user, extras)
1385 pull_request, self._rhodecode_db_user, extras)
1382 else:
1386 else:
1383 log.debug("Pre-conditions failed, NOT merging.")
1387 log.debug("Pre-conditions failed, NOT merging.")
1384
1388
1385 raise HTTPFound(
1389 raise HTTPFound(
1386 h.route_path('pullrequest_show',
1390 h.route_path('pullrequest_show',
1387 repo_name=pull_request.target_repo.repo_name,
1391 repo_name=pull_request.target_repo.repo_name,
1388 pull_request_id=pull_request.pull_request_id))
1392 pull_request_id=pull_request.pull_request_id))
1389
1393
1390 def _merge_pull_request(self, pull_request, user, extras):
1394 def _merge_pull_request(self, pull_request, user, extras):
1391 _ = self.request.translate
1395 _ = self.request.translate
1392 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1396 merge_resp = PullRequestModel().merge_repo(pull_request, user, extras=extras)
1393
1397
1394 if merge_resp.executed:
1398 if merge_resp.executed:
1395 log.debug("The merge was successful, closing the pull request.")
1399 log.debug("The merge was successful, closing the pull request.")
1396 PullRequestModel().close_pull_request(
1400 PullRequestModel().close_pull_request(
1397 pull_request.pull_request_id, user)
1401 pull_request.pull_request_id, user)
1398 Session().commit()
1402 Session().commit()
1399 msg = _('Pull request was successfully merged and closed.')
1403 msg = _('Pull request was successfully merged and closed.')
1400 h.flash(msg, category='success')
1404 h.flash(msg, category='success')
1401 else:
1405 else:
1402 log.debug(
1406 log.debug(
1403 "The merge was not successful. Merge response: %s", merge_resp)
1407 "The merge was not successful. Merge response: %s", merge_resp)
1404 msg = merge_resp.merge_status_message
1408 msg = merge_resp.merge_status_message
1405 h.flash(msg, category='error')
1409 h.flash(msg, category='error')
1406
1410
1407 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1411 def _update_reviewers(self, pull_request, review_members, reviewer_rules):
1408 _ = self.request.translate
1412 _ = self.request.translate
1409
1413
1410 get_default_reviewers_data, validate_default_reviewers = \
1414 get_default_reviewers_data, validate_default_reviewers = \
1411 PullRequestModel().get_reviewer_functions()
1415 PullRequestModel().get_reviewer_functions()
1412
1416
1413 try:
1417 try:
1414 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1418 reviewers = validate_default_reviewers(review_members, reviewer_rules)
1415 except ValueError as e:
1419 except ValueError as e:
1416 log.error('Reviewers Validation: {}'.format(e))
1420 log.error('Reviewers Validation: {}'.format(e))
1417 h.flash(e, category='error')
1421 h.flash(e, category='error')
1418 return
1422 return
1419
1423
1420 old_calculated_status = pull_request.calculated_review_status()
1424 old_calculated_status = pull_request.calculated_review_status()
1421 PullRequestModel().update_reviewers(
1425 PullRequestModel().update_reviewers(
1422 pull_request, reviewers, self._rhodecode_user)
1426 pull_request, reviewers, self._rhodecode_user)
1423 h.flash(_('Pull request reviewers updated.'), category='success')
1427 h.flash(_('Pull request reviewers updated.'), category='success')
1424 Session().commit()
1428 Session().commit()
1425
1429
1426 # trigger status changed if change in reviewers changes the status
1430 # trigger status changed if change in reviewers changes the status
1427 calculated_status = pull_request.calculated_review_status()
1431 calculated_status = pull_request.calculated_review_status()
1428 if old_calculated_status != calculated_status:
1432 if old_calculated_status != calculated_status:
1429 PullRequestModel().trigger_pull_request_hook(
1433 PullRequestModel().trigger_pull_request_hook(
1430 pull_request, self._rhodecode_user, 'review_status_change',
1434 pull_request, self._rhodecode_user, 'review_status_change',
1431 data={'status': calculated_status})
1435 data={'status': calculated_status})
1432
1436
1433 @LoginRequired()
1437 @LoginRequired()
1434 @NotAnonymous()
1438 @NotAnonymous()
1435 @HasRepoPermissionAnyDecorator(
1439 @HasRepoPermissionAnyDecorator(
1436 'repository.read', 'repository.write', 'repository.admin')
1440 'repository.read', 'repository.write', 'repository.admin')
1437 @CSRFRequired()
1441 @CSRFRequired()
1438 @view_config(
1442 @view_config(
1439 route_name='pullrequest_delete', request_method='POST',
1443 route_name='pullrequest_delete', request_method='POST',
1440 renderer='json_ext')
1444 renderer='json_ext')
1441 def pull_request_delete(self):
1445 def pull_request_delete(self):
1442 _ = self.request.translate
1446 _ = self.request.translate
1443
1447
1444 pull_request = PullRequest.get_or_404(
1448 pull_request = PullRequest.get_or_404(
1445 self.request.matchdict['pull_request_id'])
1449 self.request.matchdict['pull_request_id'])
1446 self.load_default_context()
1450 self.load_default_context()
1447
1451
1448 pr_closed = pull_request.is_closed()
1452 pr_closed = pull_request.is_closed()
1449 allowed_to_delete = PullRequestModel().check_user_delete(
1453 allowed_to_delete = PullRequestModel().check_user_delete(
1450 pull_request, self._rhodecode_user) and not pr_closed
1454 pull_request, self._rhodecode_user) and not pr_closed
1451
1455
1452 # only owner can delete it !
1456 # only owner can delete it !
1453 if allowed_to_delete:
1457 if allowed_to_delete:
1454 PullRequestModel().delete(pull_request, self._rhodecode_user)
1458 PullRequestModel().delete(pull_request, self._rhodecode_user)
1455 Session().commit()
1459 Session().commit()
1456 h.flash(_('Successfully deleted pull request'),
1460 h.flash(_('Successfully deleted pull request'),
1457 category='success')
1461 category='success')
1458 raise HTTPFound(h.route_path('pullrequest_show_all',
1462 raise HTTPFound(h.route_path('pullrequest_show_all',
1459 repo_name=self.db_repo_name))
1463 repo_name=self.db_repo_name))
1460
1464
1461 log.warning('user %s tried to delete pull request without access',
1465 log.warning('user %s tried to delete pull request without access',
1462 self._rhodecode_user)
1466 self._rhodecode_user)
1463 raise HTTPNotFound()
1467 raise HTTPNotFound()
1464
1468
1465 @LoginRequired()
1469 @LoginRequired()
1466 @NotAnonymous()
1470 @NotAnonymous()
1467 @HasRepoPermissionAnyDecorator(
1471 @HasRepoPermissionAnyDecorator(
1468 'repository.read', 'repository.write', 'repository.admin')
1472 'repository.read', 'repository.write', 'repository.admin')
1469 @CSRFRequired()
1473 @CSRFRequired()
1470 @view_config(
1474 @view_config(
1471 route_name='pullrequest_comment_create', request_method='POST',
1475 route_name='pullrequest_comment_create', request_method='POST',
1472 renderer='json_ext')
1476 renderer='json_ext')
1473 def pull_request_comment_create(self):
1477 def pull_request_comment_create(self):
1474 _ = self.request.translate
1478 _ = self.request.translate
1475
1479
1476 pull_request = PullRequest.get_or_404(
1480 pull_request = PullRequest.get_or_404(
1477 self.request.matchdict['pull_request_id'])
1481 self.request.matchdict['pull_request_id'])
1478 pull_request_id = pull_request.pull_request_id
1482 pull_request_id = pull_request.pull_request_id
1479
1483
1480 if pull_request.is_closed():
1484 if pull_request.is_closed():
1481 log.debug('comment: forbidden because pull request is closed')
1485 log.debug('comment: forbidden because pull request is closed')
1482 raise HTTPForbidden()
1486 raise HTTPForbidden()
1483
1487
1484 allowed_to_comment = PullRequestModel().check_user_comment(
1488 allowed_to_comment = PullRequestModel().check_user_comment(
1485 pull_request, self._rhodecode_user)
1489 pull_request, self._rhodecode_user)
1486 if not allowed_to_comment:
1490 if not allowed_to_comment:
1487 log.debug(
1491 log.debug(
1488 'comment: forbidden because pull request is from forbidden repo')
1492 'comment: forbidden because pull request is from forbidden repo')
1489 raise HTTPForbidden()
1493 raise HTTPForbidden()
1490
1494
1491 c = self.load_default_context()
1495 c = self.load_default_context()
1492
1496
1493 status = self.request.POST.get('changeset_status', None)
1497 status = self.request.POST.get('changeset_status', None)
1494 text = self.request.POST.get('text')
1498 text = self.request.POST.get('text')
1495 comment_type = self.request.POST.get('comment_type')
1499 comment_type = self.request.POST.get('comment_type')
1496 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1500 resolves_comment_id = self.request.POST.get('resolves_comment_id', None)
1497 close_pull_request = self.request.POST.get('close_pull_request')
1501 close_pull_request = self.request.POST.get('close_pull_request')
1498
1502
1499 # the logic here should work like following, if we submit close
1503 # the logic here should work like following, if we submit close
1500 # pr comment, use `close_pull_request_with_comment` function
1504 # pr comment, use `close_pull_request_with_comment` function
1501 # else handle regular comment logic
1505 # else handle regular comment logic
1502
1506
1503 if close_pull_request:
1507 if close_pull_request:
1504 # only owner or admin or person with write permissions
1508 # only owner or admin or person with write permissions
1505 allowed_to_close = PullRequestModel().check_user_update(
1509 allowed_to_close = PullRequestModel().check_user_update(
1506 pull_request, self._rhodecode_user)
1510 pull_request, self._rhodecode_user)
1507 if not allowed_to_close:
1511 if not allowed_to_close:
1508 log.debug('comment: forbidden because not allowed to close '
1512 log.debug('comment: forbidden because not allowed to close '
1509 'pull request %s', pull_request_id)
1513 'pull request %s', pull_request_id)
1510 raise HTTPForbidden()
1514 raise HTTPForbidden()
1511
1515
1512 # This also triggers `review_status_change`
1516 # This also triggers `review_status_change`
1513 comment, status = PullRequestModel().close_pull_request_with_comment(
1517 comment, status = PullRequestModel().close_pull_request_with_comment(
1514 pull_request, self._rhodecode_user, self.db_repo, message=text,
1518 pull_request, self._rhodecode_user, self.db_repo, message=text,
1515 auth_user=self._rhodecode_user)
1519 auth_user=self._rhodecode_user)
1516 Session().flush()
1520 Session().flush()
1517
1521
1518 PullRequestModel().trigger_pull_request_hook(
1522 PullRequestModel().trigger_pull_request_hook(
1519 pull_request, self._rhodecode_user, 'comment',
1523 pull_request, self._rhodecode_user, 'comment',
1520 data={'comment': comment})
1524 data={'comment': comment})
1521
1525
1522 else:
1526 else:
1523 # regular comment case, could be inline, or one with status.
1527 # regular comment case, could be inline, or one with status.
1524 # for that one we check also permissions
1528 # for that one we check also permissions
1525
1529
1526 allowed_to_change_status = PullRequestModel().check_user_change_status(
1530 allowed_to_change_status = PullRequestModel().check_user_change_status(
1527 pull_request, self._rhodecode_user)
1531 pull_request, self._rhodecode_user)
1528
1532
1529 if status and allowed_to_change_status:
1533 if status and allowed_to_change_status:
1530 message = (_('Status change %(transition_icon)s %(status)s')
1534 message = (_('Status change %(transition_icon)s %(status)s')
1531 % {'transition_icon': '>',
1535 % {'transition_icon': '>',
1532 'status': ChangesetStatus.get_status_lbl(status)})
1536 'status': ChangesetStatus.get_status_lbl(status)})
1533 text = text or message
1537 text = text or message
1534
1538
1535 comment = CommentsModel().create(
1539 comment = CommentsModel().create(
1536 text=text,
1540 text=text,
1537 repo=self.db_repo.repo_id,
1541 repo=self.db_repo.repo_id,
1538 user=self._rhodecode_user.user_id,
1542 user=self._rhodecode_user.user_id,
1539 pull_request=pull_request,
1543 pull_request=pull_request,
1540 f_path=self.request.POST.get('f_path'),
1544 f_path=self.request.POST.get('f_path'),
1541 line_no=self.request.POST.get('line'),
1545 line_no=self.request.POST.get('line'),
1542 status_change=(ChangesetStatus.get_status_lbl(status)
1546 status_change=(ChangesetStatus.get_status_lbl(status)
1543 if status and allowed_to_change_status else None),
1547 if status and allowed_to_change_status else None),
1544 status_change_type=(status
1548 status_change_type=(status
1545 if status and allowed_to_change_status else None),
1549 if status and allowed_to_change_status else None),
1546 comment_type=comment_type,
1550 comment_type=comment_type,
1547 resolves_comment_id=resolves_comment_id,
1551 resolves_comment_id=resolves_comment_id,
1548 auth_user=self._rhodecode_user
1552 auth_user=self._rhodecode_user
1549 )
1553 )
1550
1554
1551 if allowed_to_change_status:
1555 if allowed_to_change_status:
1552 # calculate old status before we change it
1556 # calculate old status before we change it
1553 old_calculated_status = pull_request.calculated_review_status()
1557 old_calculated_status = pull_request.calculated_review_status()
1554
1558
1555 # get status if set !
1559 # get status if set !
1556 if status:
1560 if status:
1557 ChangesetStatusModel().set_status(
1561 ChangesetStatusModel().set_status(
1558 self.db_repo.repo_id,
1562 self.db_repo.repo_id,
1559 status,
1563 status,
1560 self._rhodecode_user.user_id,
1564 self._rhodecode_user.user_id,
1561 comment,
1565 comment,
1562 pull_request=pull_request
1566 pull_request=pull_request
1563 )
1567 )
1564
1568
1565 Session().flush()
1569 Session().flush()
1566 # this is somehow required to get access to some relationship
1570 # this is somehow required to get access to some relationship
1567 # loaded on comment
1571 # loaded on comment
1568 Session().refresh(comment)
1572 Session().refresh(comment)
1569
1573
1570 PullRequestModel().trigger_pull_request_hook(
1574 PullRequestModel().trigger_pull_request_hook(
1571 pull_request, self._rhodecode_user, 'comment',
1575 pull_request, self._rhodecode_user, 'comment',
1572 data={'comment': comment})
1576 data={'comment': comment})
1573
1577
1574 # we now calculate the status of pull request, and based on that
1578 # we now calculate the status of pull request, and based on that
1575 # calculation we set the commits status
1579 # calculation we set the commits status
1576 calculated_status = pull_request.calculated_review_status()
1580 calculated_status = pull_request.calculated_review_status()
1577 if old_calculated_status != calculated_status:
1581 if old_calculated_status != calculated_status:
1578 PullRequestModel().trigger_pull_request_hook(
1582 PullRequestModel().trigger_pull_request_hook(
1579 pull_request, self._rhodecode_user, 'review_status_change',
1583 pull_request, self._rhodecode_user, 'review_status_change',
1580 data={'status': calculated_status})
1584 data={'status': calculated_status})
1581
1585
1582 Session().commit()
1586 Session().commit()
1583
1587
1584 data = {
1588 data = {
1585 'target_id': h.safeid(h.safe_unicode(
1589 'target_id': h.safeid(h.safe_unicode(
1586 self.request.POST.get('f_path'))),
1590 self.request.POST.get('f_path'))),
1587 }
1591 }
1588 if comment:
1592 if comment:
1589 c.co = comment
1593 c.co = comment
1590 c.at_version_num = None
1594 c.at_version_num = None
1591 rendered_comment = render(
1595 rendered_comment = render(
1592 'rhodecode:templates/changeset/changeset_comment_block.mako',
1596 'rhodecode:templates/changeset/changeset_comment_block.mako',
1593 self._get_template_context(c), self.request)
1597 self._get_template_context(c), self.request)
1594
1598
1595 data.update(comment.get_dict())
1599 data.update(comment.get_dict())
1596 data.update({'rendered_text': rendered_comment})
1600 data.update({'rendered_text': rendered_comment})
1597
1601
1598 return data
1602 return data
1599
1603
1600 @LoginRequired()
1604 @LoginRequired()
1601 @NotAnonymous()
1605 @NotAnonymous()
1602 @HasRepoPermissionAnyDecorator(
1606 @HasRepoPermissionAnyDecorator(
1603 'repository.read', 'repository.write', 'repository.admin')
1607 'repository.read', 'repository.write', 'repository.admin')
1604 @CSRFRequired()
1608 @CSRFRequired()
1605 @view_config(
1609 @view_config(
1606 route_name='pullrequest_comment_delete', request_method='POST',
1610 route_name='pullrequest_comment_delete', request_method='POST',
1607 renderer='json_ext')
1611 renderer='json_ext')
1608 def pull_request_comment_delete(self):
1612 def pull_request_comment_delete(self):
1609 pull_request = PullRequest.get_or_404(
1613 pull_request = PullRequest.get_or_404(
1610 self.request.matchdict['pull_request_id'])
1614 self.request.matchdict['pull_request_id'])
1611
1615
1612 comment = ChangesetComment.get_or_404(
1616 comment = ChangesetComment.get_or_404(
1613 self.request.matchdict['comment_id'])
1617 self.request.matchdict['comment_id'])
1614 comment_id = comment.comment_id
1618 comment_id = comment.comment_id
1615
1619
1616 if comment.immutable:
1620 if comment.immutable:
1617 # don't allow deleting comments that are immutable
1621 # don't allow deleting comments that are immutable
1618 raise HTTPForbidden()
1622 raise HTTPForbidden()
1619
1623
1620 if pull_request.is_closed():
1624 if pull_request.is_closed():
1621 log.debug('comment: forbidden because pull request is closed')
1625 log.debug('comment: forbidden because pull request is closed')
1622 raise HTTPForbidden()
1626 raise HTTPForbidden()
1623
1627
1624 if not comment:
1628 if not comment:
1625 log.debug('Comment with id:%s not found, skipping', comment_id)
1629 log.debug('Comment with id:%s not found, skipping', comment_id)
1626 # comment already deleted in another call probably
1630 # comment already deleted in another call probably
1627 return True
1631 return True
1628
1632
1629 if comment.pull_request.is_closed():
1633 if comment.pull_request.is_closed():
1630 # don't allow deleting comments on closed pull request
1634 # don't allow deleting comments on closed pull request
1631 raise HTTPForbidden()
1635 raise HTTPForbidden()
1632
1636
1633 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1637 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1634 super_admin = h.HasPermissionAny('hg.admin')()
1638 super_admin = h.HasPermissionAny('hg.admin')()
1635 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1639 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1636 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1640 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1637 comment_repo_admin = is_repo_admin and is_repo_comment
1641 comment_repo_admin = is_repo_admin and is_repo_comment
1638
1642
1639 if super_admin or comment_owner or comment_repo_admin:
1643 if super_admin or comment_owner or comment_repo_admin:
1640 old_calculated_status = comment.pull_request.calculated_review_status()
1644 old_calculated_status = comment.pull_request.calculated_review_status()
1641 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1645 CommentsModel().delete(comment=comment, auth_user=self._rhodecode_user)
1642 Session().commit()
1646 Session().commit()
1643 calculated_status = comment.pull_request.calculated_review_status()
1647 calculated_status = comment.pull_request.calculated_review_status()
1644 if old_calculated_status != calculated_status:
1648 if old_calculated_status != calculated_status:
1645 PullRequestModel().trigger_pull_request_hook(
1649 PullRequestModel().trigger_pull_request_hook(
1646 comment.pull_request, self._rhodecode_user, 'review_status_change',
1650 comment.pull_request, self._rhodecode_user, 'review_status_change',
1647 data={'status': calculated_status})
1651 data={'status': calculated_status})
1648 return True
1652 return True
1649 else:
1653 else:
1650 log.warning('No permissions for user %s to delete comment_id: %s',
1654 log.warning('No permissions for user %s to delete comment_id: %s',
1651 self._rhodecode_db_user, comment_id)
1655 self._rhodecode_db_user, comment_id)
1652 raise HTTPNotFound()
1656 raise HTTPNotFound()
1653
1657
1654 @LoginRequired()
1658 @LoginRequired()
1655 @NotAnonymous()
1659 @NotAnonymous()
1656 @HasRepoPermissionAnyDecorator(
1660 @HasRepoPermissionAnyDecorator(
1657 'repository.read', 'repository.write', 'repository.admin')
1661 'repository.read', 'repository.write', 'repository.admin')
1658 @CSRFRequired()
1662 @CSRFRequired()
1659 @view_config(
1663 @view_config(
1660 route_name='pullrequest_comment_edit', request_method='POST',
1664 route_name='pullrequest_comment_edit', request_method='POST',
1661 renderer='json_ext')
1665 renderer='json_ext')
1662 def pull_request_comment_edit(self):
1666 def pull_request_comment_edit(self):
1663 self.load_default_context()
1667 self.load_default_context()
1664
1668
1665 pull_request = PullRequest.get_or_404(
1669 pull_request = PullRequest.get_or_404(
1666 self.request.matchdict['pull_request_id']
1670 self.request.matchdict['pull_request_id']
1667 )
1671 )
1668 comment = ChangesetComment.get_or_404(
1672 comment = ChangesetComment.get_or_404(
1669 self.request.matchdict['comment_id']
1673 self.request.matchdict['comment_id']
1670 )
1674 )
1671 comment_id = comment.comment_id
1675 comment_id = comment.comment_id
1672
1676
1673 if comment.immutable:
1677 if comment.immutable:
1674 # don't allow deleting comments that are immutable
1678 # don't allow deleting comments that are immutable
1675 raise HTTPForbidden()
1679 raise HTTPForbidden()
1676
1680
1677 if pull_request.is_closed():
1681 if pull_request.is_closed():
1678 log.debug('comment: forbidden because pull request is closed')
1682 log.debug('comment: forbidden because pull request is closed')
1679 raise HTTPForbidden()
1683 raise HTTPForbidden()
1680
1684
1681 if not comment:
1685 if not comment:
1682 log.debug('Comment with id:%s not found, skipping', comment_id)
1686 log.debug('Comment with id:%s not found, skipping', comment_id)
1683 # comment already deleted in another call probably
1687 # comment already deleted in another call probably
1684 return True
1688 return True
1685
1689
1686 if comment.pull_request.is_closed():
1690 if comment.pull_request.is_closed():
1687 # don't allow deleting comments on closed pull request
1691 # don't allow deleting comments on closed pull request
1688 raise HTTPForbidden()
1692 raise HTTPForbidden()
1689
1693
1690 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1694 is_repo_admin = h.HasRepoPermissionAny('repository.admin')(self.db_repo_name)
1691 super_admin = h.HasPermissionAny('hg.admin')()
1695 super_admin = h.HasPermissionAny('hg.admin')()
1692 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1696 comment_owner = comment.author.user_id == self._rhodecode_user.user_id
1693 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1697 is_repo_comment = comment.repo.repo_name == self.db_repo_name
1694 comment_repo_admin = is_repo_admin and is_repo_comment
1698 comment_repo_admin = is_repo_admin and is_repo_comment
1695
1699
1696 if super_admin or comment_owner or comment_repo_admin:
1700 if super_admin or comment_owner or comment_repo_admin:
1697 text = self.request.POST.get('text')
1701 text = self.request.POST.get('text')
1698 version = self.request.POST.get('version')
1702 version = self.request.POST.get('version')
1699 if text == comment.text:
1703 if text == comment.text:
1700 log.warning(
1704 log.warning(
1701 'Comment(PR): '
1705 'Comment(PR): '
1702 'Trying to create new version '
1706 'Trying to create new version '
1703 'with the same comment body {}'.format(
1707 'with the same comment body {}'.format(
1704 comment_id,
1708 comment_id,
1705 )
1709 )
1706 )
1710 )
1707 raise HTTPNotFound()
1711 raise HTTPNotFound()
1708
1712
1709 if version.isdigit():
1713 if version.isdigit():
1710 version = int(version)
1714 version = int(version)
1711 else:
1715 else:
1712 log.warning(
1716 log.warning(
1713 'Comment(PR): Wrong version type {} {} '
1717 'Comment(PR): Wrong version type {} {} '
1714 'for comment {}'.format(
1718 'for comment {}'.format(
1715 version,
1719 version,
1716 type(version),
1720 type(version),
1717 comment_id,
1721 comment_id,
1718 )
1722 )
1719 )
1723 )
1720 raise HTTPNotFound()
1724 raise HTTPNotFound()
1721
1725
1722 try:
1726 try:
1723 comment_history = CommentsModel().edit(
1727 comment_history = CommentsModel().edit(
1724 comment_id=comment_id,
1728 comment_id=comment_id,
1725 text=text,
1729 text=text,
1726 auth_user=self._rhodecode_user,
1730 auth_user=self._rhodecode_user,
1727 version=version,
1731 version=version,
1728 )
1732 )
1729 except CommentVersionMismatch:
1733 except CommentVersionMismatch:
1730 raise HTTPConflict()
1734 raise HTTPConflict()
1731
1735
1732 if not comment_history:
1736 if not comment_history:
1733 raise HTTPNotFound()
1737 raise HTTPNotFound()
1734
1738
1735 Session().commit()
1739 Session().commit()
1736
1740
1737 PullRequestModel().trigger_pull_request_hook(
1741 PullRequestModel().trigger_pull_request_hook(
1738 pull_request, self._rhodecode_user, 'comment_edit',
1742 pull_request, self._rhodecode_user, 'comment_edit',
1739 data={'comment': comment})
1743 data={'comment': comment})
1740
1744
1741 return {
1745 return {
1742 'comment_history_id': comment_history.comment_history_id,
1746 'comment_history_id': comment_history.comment_history_id,
1743 'comment_id': comment.comment_id,
1747 'comment_id': comment.comment_id,
1744 'comment_version': comment_history.version,
1748 'comment_version': comment_history.version,
1745 'comment_author_username': comment_history.author.username,
1749 'comment_author_username': comment_history.author.username,
1746 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
1750 'comment_author_gravatar': h.gravatar_url(comment_history.author.email, 16),
1747 'comment_created_on': h.age_component(comment_history.created_on,
1751 'comment_created_on': h.age_component(comment_history.created_on,
1748 time_is_local=True),
1752 time_is_local=True),
1749 }
1753 }
1750 else:
1754 else:
1751 log.warning('No permissions for user %s to edit comment_id: %s',
1755 log.warning('No permissions for user %s to edit comment_id: %s',
1752 self._rhodecode_db_user, comment_id)
1756 self._rhodecode_db_user, comment_id)
1753 raise HTTPNotFound()
1757 raise HTTPNotFound()
@@ -1,394 +1,397 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2020 RhodeCode GmbH
3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21
21
22 import itertools
22 import itertools
23 import logging
23 import logging
24 import collections
24 import collections
25
25
26 from rhodecode.model import BaseModel
26 from rhodecode.model import BaseModel
27 from rhodecode.model.db import (
27 from rhodecode.model.db import (
28 ChangesetStatus, ChangesetComment, PullRequest, Session)
28 ChangesetStatus, ChangesetComment, PullRequest, Session)
29 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
29 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
30 from rhodecode.lib.markup_renderer import (
30 from rhodecode.lib.markup_renderer import (
31 DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer)
31 DEFAULT_COMMENTS_RENDERER, RstTemplateRenderer)
32
32
33 log = logging.getLogger(__name__)
33 log = logging.getLogger(__name__)
34
34
35
35
36 class ChangesetStatusModel(BaseModel):
36 class ChangesetStatusModel(BaseModel):
37
37
38 cls = ChangesetStatus
38 cls = ChangesetStatus
39
39
40 def __get_changeset_status(self, changeset_status):
40 def __get_changeset_status(self, changeset_status):
41 return self._get_instance(ChangesetStatus, changeset_status)
41 return self._get_instance(ChangesetStatus, changeset_status)
42
42
43 def __get_pull_request(self, pull_request):
43 def __get_pull_request(self, pull_request):
44 return self._get_instance(PullRequest, pull_request)
44 return self._get_instance(PullRequest, pull_request)
45
45
46 def _get_status_query(self, repo, revision, pull_request,
46 def _get_status_query(self, repo, revision, pull_request,
47 with_revisions=False):
47 with_revisions=False):
48 repo = self._get_repo(repo)
48 repo = self._get_repo(repo)
49
49
50 q = ChangesetStatus.query()\
50 q = ChangesetStatus.query()\
51 .filter(ChangesetStatus.repo == repo)
51 .filter(ChangesetStatus.repo == repo)
52 if not with_revisions:
52 if not with_revisions:
53 q = q.filter(ChangesetStatus.version == 0)
53 q = q.filter(ChangesetStatus.version == 0)
54
54
55 if revision:
55 if revision:
56 q = q.filter(ChangesetStatus.revision == revision)
56 q = q.filter(ChangesetStatus.revision == revision)
57 elif pull_request:
57 elif pull_request:
58 pull_request = self.__get_pull_request(pull_request)
58 pull_request = self.__get_pull_request(pull_request)
59 # TODO: johbo: Think about the impact of this join, there must
59 # TODO: johbo: Think about the impact of this join, there must
60 # be a reason why ChangesetStatus and ChanagesetComment is linked
60 # be a reason why ChangesetStatus and ChanagesetComment is linked
61 # to the pull request. Might be that we want to do the same for
61 # to the pull request. Might be that we want to do the same for
62 # the pull_request_version_id.
62 # the pull_request_version_id.
63 q = q.join(ChangesetComment).filter(
63 q = q.join(ChangesetComment).filter(
64 ChangesetStatus.pull_request == pull_request,
64 ChangesetStatus.pull_request == pull_request,
65 ChangesetComment.pull_request_version_id == None)
65 ChangesetComment.pull_request_version_id == None)
66 else:
66 else:
67 raise Exception('Please specify revision or pull_request')
67 raise Exception('Please specify revision or pull_request')
68 q = q.order_by(ChangesetStatus.version.asc())
68 q = q.order_by(ChangesetStatus.version.asc())
69 return q
69 return q
70
70
71 def calculate_group_vote(self, group_id, group_statuses_by_reviewers,
71 def calculate_group_vote(self, group_id, group_statuses_by_reviewers,
72 trim_votes=True):
72 trim_votes=True):
73 """
73 """
74 Calculate status based on given group members, and voting rule
74 Calculate status based on given group members, and voting rule
75
75
76
76
77 group1 - 4 members, 3 required for approval
77 group1 - 4 members, 3 required for approval
78 user1 - approved
78 user1 - approved
79 user2 - reject
79 user2 - reject
80 user3 - approved
80 user3 - approved
81 user4 - rejected
81 user4 - rejected
82
82
83 final_state: rejected, reasons not at least 3 votes
83 final_state: rejected, reasons not at least 3 votes
84
84
85
85
86 group1 - 4 members, 2 required for approval
86 group1 - 4 members, 2 required for approval
87 user1 - approved
87 user1 - approved
88 user2 - reject
88 user2 - reject
89 user3 - approved
89 user3 - approved
90 user4 - rejected
90 user4 - rejected
91
91
92 final_state: approved, reasons got at least 2 approvals
92 final_state: approved, reasons got at least 2 approvals
93
93
94 group1 - 4 members, ALL required for approval
94 group1 - 4 members, ALL required for approval
95 user1 - approved
95 user1 - approved
96 user2 - reject
96 user2 - reject
97 user3 - approved
97 user3 - approved
98 user4 - rejected
98 user4 - rejected
99
99
100 final_state: rejected, reasons not all approvals
100 final_state: rejected, reasons not all approvals
101
101
102
102
103 group1 - 4 members, ALL required for approval
103 group1 - 4 members, ALL required for approval
104 user1 - approved
104 user1 - approved
105 user2 - approved
105 user2 - approved
106 user3 - approved
106 user3 - approved
107 user4 - approved
107 user4 - approved
108
108
109 final_state: approved, reason all approvals received
109 final_state: approved, reason all approvals received
110
110
111 group1 - 4 members, 5 required for approval
111 group1 - 4 members, 5 required for approval
112 (approval should be shorted to number of actual members)
112 (approval should be shorted to number of actual members)
113
113
114 user1 - approved
114 user1 - approved
115 user2 - approved
115 user2 - approved
116 user3 - approved
116 user3 - approved
117 user4 - approved
117 user4 - approved
118
118
119 final_state: approved, reason all approvals received
119 final_state: approved, reason all approvals received
120
120
121 """
121 """
122 group_vote_data = {}
122 group_vote_data = {}
123 got_rule = False
123 got_rule = False
124 members = collections.OrderedDict()
124 members = collections.OrderedDict()
125 for review_obj, user, reasons, mandatory, statuses \
125 for review_obj, user, reasons, mandatory, statuses \
126 in group_statuses_by_reviewers:
126 in group_statuses_by_reviewers:
127
127
128 if not got_rule:
128 if not got_rule:
129 group_vote_data = review_obj.rule_user_group_data()
129 group_vote_data = review_obj.rule_user_group_data()
130 got_rule = bool(group_vote_data)
130 got_rule = bool(group_vote_data)
131
131
132 members[user.user_id] = statuses
132 members[user.user_id] = statuses
133
133
134 if not group_vote_data:
134 if not group_vote_data:
135 return []
135 return []
136
136
137 required_votes = group_vote_data['vote_rule']
137 required_votes = group_vote_data['vote_rule']
138 if required_votes == -1:
138 if required_votes == -1:
139 # -1 means all required, so we replace it with how many people
139 # -1 means all required, so we replace it with how many people
140 # are in the members
140 # are in the members
141 required_votes = len(members)
141 required_votes = len(members)
142
142
143 if trim_votes and required_votes > len(members):
143 if trim_votes and required_votes > len(members):
144 # we require more votes than we have members in the group
144 # we require more votes than we have members in the group
145 # in this case we trim the required votes to the number of members
145 # in this case we trim the required votes to the number of members
146 required_votes = len(members)
146 required_votes = len(members)
147
147
148 approvals = sum([
148 approvals = sum([
149 1 for statuses in members.values()
149 1 for statuses in members.values()
150 if statuses and
150 if statuses and
151 statuses[0][1].status == ChangesetStatus.STATUS_APPROVED])
151 statuses[0][1].status == ChangesetStatus.STATUS_APPROVED])
152
152
153 calculated_votes = []
153 calculated_votes = []
154 # we have all votes from users, now check if we have enough votes
154 # we have all votes from users, now check if we have enough votes
155 # to fill other
155 # to fill other
156 fill_in = ChangesetStatus.STATUS_UNDER_REVIEW
156 fill_in = ChangesetStatus.STATUS_UNDER_REVIEW
157 if approvals >= required_votes:
157 if approvals >= required_votes:
158 fill_in = ChangesetStatus.STATUS_APPROVED
158 fill_in = ChangesetStatus.STATUS_APPROVED
159
159
160 for member, statuses in members.items():
160 for member, statuses in members.items():
161 if statuses:
161 if statuses:
162 ver, latest = statuses[0]
162 ver, latest = statuses[0]
163 if fill_in == ChangesetStatus.STATUS_APPROVED:
163 if fill_in == ChangesetStatus.STATUS_APPROVED:
164 calculated_votes.append(fill_in)
164 calculated_votes.append(fill_in)
165 else:
165 else:
166 calculated_votes.append(latest.status)
166 calculated_votes.append(latest.status)
167 else:
167 else:
168 calculated_votes.append(fill_in)
168 calculated_votes.append(fill_in)
169
169
170 return calculated_votes
170 return calculated_votes
171
171
172 def calculate_status(self, statuses_by_reviewers):
172 def calculate_status(self, statuses_by_reviewers):
173 """
173 """
174 Given the approval statuses from reviewers, calculates final approval
174 Given the approval statuses from reviewers, calculates final approval
175 status. There can only be 3 results, all approved, all rejected. If
175 status. There can only be 3 results, all approved, all rejected. If
176 there is no consensus the PR is under review.
176 there is no consensus the PR is under review.
177
177
178 :param statuses_by_reviewers:
178 :param statuses_by_reviewers:
179 """
179 """
180
180
181 def group_rule(element):
181 def group_rule(element):
182 review_obj = element[0]
182 review_obj = element[0]
183 rule_data = review_obj.rule_user_group_data()
183 rule_data = review_obj.rule_user_group_data()
184 if rule_data and rule_data['id']:
184 if rule_data and rule_data['id']:
185 return rule_data['id']
185 return rule_data['id']
186
186
187 voting_groups = itertools.groupby(
187 voting_groups = itertools.groupby(
188 sorted(statuses_by_reviewers, key=group_rule), group_rule)
188 sorted(statuses_by_reviewers, key=group_rule), group_rule)
189
189
190 voting_by_groups = [(x, list(y)) for x, y in voting_groups]
190 voting_by_groups = [(x, list(y)) for x, y in voting_groups]
191
191
192 reviewers_number = len(statuses_by_reviewers)
192 reviewers_number = len(statuses_by_reviewers)
193 votes = collections.defaultdict(int)
193 votes = collections.defaultdict(int)
194 for group, group_statuses_by_reviewers in voting_by_groups:
194 for group, group_statuses_by_reviewers in voting_by_groups:
195 if group:
195 if group:
196 # calculate how the "group" voted
196 # calculate how the "group" voted
197 for vote_status in self.calculate_group_vote(
197 for vote_status in self.calculate_group_vote(
198 group, group_statuses_by_reviewers):
198 group, group_statuses_by_reviewers):
199 votes[vote_status] += 1
199 votes[vote_status] += 1
200 else:
200 else:
201
201
202 for review_obj, user, reasons, mandatory, statuses \
202 for review_obj, user, reasons, mandatory, statuses \
203 in group_statuses_by_reviewers:
203 in group_statuses_by_reviewers:
204 # individual vote
204 # individual vote
205 if statuses:
205 if statuses:
206 ver, latest = statuses[0]
206 ver, latest = statuses[0]
207 votes[latest.status] += 1
207 votes[latest.status] += 1
208
208
209 approved_votes_count = votes[ChangesetStatus.STATUS_APPROVED]
209 approved_votes_count = votes[ChangesetStatus.STATUS_APPROVED]
210 rejected_votes_count = votes[ChangesetStatus.STATUS_REJECTED]
210 rejected_votes_count = votes[ChangesetStatus.STATUS_REJECTED]
211
211
212 # TODO(marcink): with group voting, how does rejected work,
212 # TODO(marcink): with group voting, how does rejected work,
213 # do we ever get rejected state ?
213 # do we ever get rejected state ?
214
214
215 if approved_votes_count == reviewers_number:
215 if approved_votes_count == reviewers_number:
216 return ChangesetStatus.STATUS_APPROVED
216 return ChangesetStatus.STATUS_APPROVED
217
217
218 if rejected_votes_count == reviewers_number:
218 if rejected_votes_count == reviewers_number:
219 return ChangesetStatus.STATUS_REJECTED
219 return ChangesetStatus.STATUS_REJECTED
220
220
221 return ChangesetStatus.STATUS_UNDER_REVIEW
221 return ChangesetStatus.STATUS_UNDER_REVIEW
222
222
223 def get_statuses(self, repo, revision=None, pull_request=None,
223 def get_statuses(self, repo, revision=None, pull_request=None,
224 with_revisions=False):
224 with_revisions=False):
225 q = self._get_status_query(repo, revision, pull_request,
225 q = self._get_status_query(repo, revision, pull_request,
226 with_revisions)
226 with_revisions)
227 return q.all()
227 return q.all()
228
228
229 def get_status(self, repo, revision=None, pull_request=None, as_str=True):
229 def get_status(self, repo, revision=None, pull_request=None, as_str=True):
230 """
230 """
231 Returns latest status of changeset for given revision or for given
231 Returns latest status of changeset for given revision or for given
232 pull request. Statuses are versioned inside a table itself and
232 pull request. Statuses are versioned inside a table itself and
233 version == 0 is always the current one
233 version == 0 is always the current one
234
234
235 :param repo:
235 :param repo:
236 :param revision: 40char hash or None
236 :param revision: 40char hash or None
237 :param pull_request: pull_request reference
237 :param pull_request: pull_request reference
238 :param as_str: return status as string not object
238 :param as_str: return status as string not object
239 """
239 """
240 q = self._get_status_query(repo, revision, pull_request)
240 q = self._get_status_query(repo, revision, pull_request)
241
241
242 # need to use first here since there can be multiple statuses
242 # need to use first here since there can be multiple statuses
243 # returned from pull_request
243 # returned from pull_request
244 status = q.first()
244 status = q.first()
245 if as_str:
245 if as_str:
246 status = status.status if status else status
246 status = status.status if status else status
247 st = status or ChangesetStatus.DEFAULT
247 st = status or ChangesetStatus.DEFAULT
248 return str(st)
248 return str(st)
249 return status
249 return status
250
250
251 def _render_auto_status_message(
251 def _render_auto_status_message(
252 self, status, commit_id=None, pull_request=None):
252 self, status, commit_id=None, pull_request=None):
253 """
253 """
254 render the message using DEFAULT_COMMENTS_RENDERER (RST renderer),
254 render the message using DEFAULT_COMMENTS_RENDERER (RST renderer),
255 so it's always looking the same disregarding on which default
255 so it's always looking the same disregarding on which default
256 renderer system is using.
256 renderer system is using.
257
257
258 :param status: status text to change into
258 :param status: status text to change into
259 :param commit_id: the commit_id we change the status for
259 :param commit_id: the commit_id we change the status for
260 :param pull_request: the pull request we change the status for
260 :param pull_request: the pull request we change the status for
261 """
261 """
262
262
263 new_status = ChangesetStatus.get_status_lbl(status)
263 new_status = ChangesetStatus.get_status_lbl(status)
264
264
265 params = {
265 params = {
266 'new_status_label': new_status,
266 'new_status_label': new_status,
267 'pull_request': pull_request,
267 'pull_request': pull_request,
268 'commit_id': commit_id,
268 'commit_id': commit_id,
269 }
269 }
270 renderer = RstTemplateRenderer()
270 renderer = RstTemplateRenderer()
271 return renderer.render('auto_status_change.mako', **params)
271 return renderer.render('auto_status_change.mako', **params)
272
272
273 def set_status(self, repo, status, user, comment=None, revision=None,
273 def set_status(self, repo, status, user, comment=None, revision=None,
274 pull_request=None, dont_allow_on_closed_pull_request=False):
274 pull_request=None, dont_allow_on_closed_pull_request=False):
275 """
275 """
276 Creates new status for changeset or updates the old ones bumping their
276 Creates new status for changeset or updates the old ones bumping their
277 version, leaving the current status at
277 version, leaving the current status at
278
278
279 :param repo:
279 :param repo:
280 :param revision:
280 :param revision:
281 :param status:
281 :param status:
282 :param user:
282 :param user:
283 :param comment:
283 :param comment:
284 :param dont_allow_on_closed_pull_request: don't allow a status change
284 :param dont_allow_on_closed_pull_request: don't allow a status change
285 if last status was for pull request and it's closed. We shouldn't
285 if last status was for pull request and it's closed. We shouldn't
286 mess around this manually
286 mess around this manually
287 """
287 """
288 repo = self._get_repo(repo)
288 repo = self._get_repo(repo)
289
289
290 q = ChangesetStatus.query()
290 q = ChangesetStatus.query()
291
291
292 if revision:
292 if revision:
293 q = q.filter(ChangesetStatus.repo == repo)
293 q = q.filter(ChangesetStatus.repo == repo)
294 q = q.filter(ChangesetStatus.revision == revision)
294 q = q.filter(ChangesetStatus.revision == revision)
295 elif pull_request:
295 elif pull_request:
296 pull_request = self.__get_pull_request(pull_request)
296 pull_request = self.__get_pull_request(pull_request)
297 q = q.filter(ChangesetStatus.repo == pull_request.source_repo)
297 q = q.filter(ChangesetStatus.repo == pull_request.source_repo)
298 q = q.filter(ChangesetStatus.revision.in_(pull_request.revisions))
298 q = q.filter(ChangesetStatus.revision.in_(pull_request.revisions))
299 cur_statuses = q.all()
299 cur_statuses = q.all()
300
300
301 # if statuses exists and last is associated with a closed pull request
301 # if statuses exists and last is associated with a closed pull request
302 # we need to check if we can allow this status change
302 # we need to check if we can allow this status change
303 if (dont_allow_on_closed_pull_request and cur_statuses
303 if (dont_allow_on_closed_pull_request and cur_statuses
304 and getattr(cur_statuses[0].pull_request, 'status', '')
304 and getattr(cur_statuses[0].pull_request, 'status', '')
305 == PullRequest.STATUS_CLOSED):
305 == PullRequest.STATUS_CLOSED):
306 raise StatusChangeOnClosedPullRequestError(
306 raise StatusChangeOnClosedPullRequestError(
307 'Changing status on closed pull request is not allowed'
307 'Changing status on closed pull request is not allowed'
308 )
308 )
309
309
310 # update all current statuses with older version
310 # update all current statuses with older version
311 if cur_statuses:
311 if cur_statuses:
312 for st in cur_statuses:
312 for st in cur_statuses:
313 st.version += 1
313 st.version += 1
314 Session().add(st)
314 Session().add(st)
315 Session().flush()
315 Session().flush()
316
316
317 def _create_status(user, repo, status, comment, revision, pull_request):
317 def _create_status(user, repo, status, comment, revision, pull_request):
318 new_status = ChangesetStatus()
318 new_status = ChangesetStatus()
319 new_status.author = self._get_user(user)
319 new_status.author = self._get_user(user)
320 new_status.repo = self._get_repo(repo)
320 new_status.repo = self._get_repo(repo)
321 new_status.status = status
321 new_status.status = status
322 new_status.comment = comment
322 new_status.comment = comment
323 new_status.revision = revision
323 new_status.revision = revision
324 new_status.pull_request = pull_request
324 new_status.pull_request = pull_request
325 return new_status
325 return new_status
326
326
327 if not comment:
327 if not comment:
328 from rhodecode.model.comment import CommentsModel
328 from rhodecode.model.comment import CommentsModel
329 comment = CommentsModel().create(
329 comment = CommentsModel().create(
330 text=self._render_auto_status_message(
330 text=self._render_auto_status_message(
331 status, commit_id=revision, pull_request=pull_request),
331 status, commit_id=revision, pull_request=pull_request),
332 repo=repo,
332 repo=repo,
333 user=user,
333 user=user,
334 pull_request=pull_request,
334 pull_request=pull_request,
335 send_email=False, renderer=DEFAULT_COMMENTS_RENDERER
335 send_email=False, renderer=DEFAULT_COMMENTS_RENDERER
336 )
336 )
337
337
338 if revision:
338 if revision:
339 new_status = _create_status(
339 new_status = _create_status(
340 user=user, repo=repo, status=status, comment=comment,
340 user=user, repo=repo, status=status, comment=comment,
341 revision=revision, pull_request=pull_request)
341 revision=revision, pull_request=pull_request)
342 Session().add(new_status)
342 Session().add(new_status)
343 return new_status
343 return new_status
344 elif pull_request:
344 elif pull_request:
345 # pull request can have more than one revision associated to it
345 # pull request can have more than one revision associated to it
346 # we need to create new version for each one
346 # we need to create new version for each one
347 new_statuses = []
347 new_statuses = []
348 repo = pull_request.source_repo
348 repo = pull_request.source_repo
349 for rev in pull_request.revisions:
349 for rev in pull_request.revisions:
350 new_status = _create_status(
350 new_status = _create_status(
351 user=user, repo=repo, status=status, comment=comment,
351 user=user, repo=repo, status=status, comment=comment,
352 revision=rev, pull_request=pull_request)
352 revision=rev, pull_request=pull_request)
353 new_statuses.append(new_status)
353 new_statuses.append(new_status)
354 Session().add(new_status)
354 Session().add(new_status)
355 return new_statuses
355 return new_statuses
356
356
357 def aggregate_votes_by_user(self, commit_statuses, reviewers_data):
358
359 commit_statuses_map = collections.defaultdict(list)
360 for st in commit_statuses:
361 commit_statuses_map[st.author.username] += [st]
362
363 reviewers = []
364
365 def version(commit_status):
366 return commit_status.version
367
368 for obj in reviewers_data:
369 if not obj.user:
370 continue
371 statuses = commit_statuses_map.get(obj.user.username, None)
372 if statuses:
373 status_groups = itertools.groupby(
374 sorted(statuses, key=version), version)
375 statuses = [(x, list(y)[0]) for x, y in status_groups]
376
377 reviewers.append((obj, obj.user, obj.reasons, obj.mandatory, statuses))
378
379 return reviewers
380
357 def reviewers_statuses(self, pull_request):
381 def reviewers_statuses(self, pull_request):
358 _commit_statuses = self.get_statuses(
382 _commit_statuses = self.get_statuses(
359 pull_request.source_repo,
383 pull_request.source_repo,
360 pull_request=pull_request,
384 pull_request=pull_request,
361 with_revisions=True)
385 with_revisions=True)
362
386
363 commit_statuses = collections.defaultdict(list)
387 return self.aggregate_votes_by_user(_commit_statuses, pull_request.reviewers)
364 for st in _commit_statuses:
365 commit_statuses[st.author.username] += [st]
366
367 pull_request_reviewers = []
368
369 def version(commit_status):
370 return commit_status.version
371
372 for obj in pull_request.reviewers:
373 if not obj.user:
374 continue
375 statuses = commit_statuses.get(obj.user.username, None)
376 if statuses:
377 status_groups = itertools.groupby(
378 sorted(statuses, key=version), version)
379 statuses = [(x, list(y)[0]) for x, y in status_groups]
380
381 pull_request_reviewers.append(
382 (obj, obj.user, obj.reasons, obj.mandatory, statuses))
383
384 return pull_request_reviewers
385
388
386 def calculated_review_status(self, pull_request, reviewers_statuses=None):
389 def calculated_review_status(self, pull_request, reviewers_statuses=None):
387 """
390 """
388 calculate pull request status based on reviewers, it should be a list
391 calculate pull request status based on reviewers, it should be a list
389 of two element lists.
392 of two element lists.
390
393
391 :param reviewers_statuses:
394 :param reviewers_statuses:
392 """
395 """
393 reviewers = reviewers_statuses or self.reviewers_statuses(pull_request)
396 reviewers = reviewers_statuses or self.reviewers_statuses(pull_request)
394 return self.calculate_status(reviewers)
397 return self.calculate_status(reviewers)
@@ -1,855 +1,863 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2020 RhodeCode GmbH
3 # Copyright (C) 2011-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 comments model for RhodeCode
22 comments model for RhodeCode
23 """
23 """
24 import datetime
24 import datetime
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import collections
28 import collections
29
29
30 from pyramid.threadlocal import get_current_registry, get_current_request
30 from pyramid.threadlocal import get_current_registry, get_current_request
31 from sqlalchemy.sql.expression import null
31 from sqlalchemy.sql.expression import null
32 from sqlalchemy.sql.functions import coalesce
32 from sqlalchemy.sql.functions import coalesce
33
33
34 from rhodecode.lib import helpers as h, diffs, channelstream, hooks_utils
34 from rhodecode.lib import helpers as h, diffs, channelstream, hooks_utils
35 from rhodecode.lib import audit_logger
35 from rhodecode.lib import audit_logger
36 from rhodecode.lib.exceptions import CommentVersionMismatch
36 from rhodecode.lib.exceptions import CommentVersionMismatch
37 from rhodecode.lib.utils2 import extract_mentioned_users, safe_str, safe_int
37 from rhodecode.lib.utils2 import extract_mentioned_users, safe_str, safe_int
38 from rhodecode.model import BaseModel
38 from rhodecode.model import BaseModel
39 from rhodecode.model.db import (
39 from rhodecode.model.db import (
40 ChangesetComment,
40 ChangesetComment,
41 User,
41 User,
42 Notification,
42 Notification,
43 PullRequest,
43 PullRequest,
44 AttributeDict,
44 AttributeDict,
45 ChangesetCommentHistory,
45 ChangesetCommentHistory,
46 )
46 )
47 from rhodecode.model.notification import NotificationModel
47 from rhodecode.model.notification import NotificationModel
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.model.settings import VcsSettingsModel
49 from rhodecode.model.settings import VcsSettingsModel
50 from rhodecode.model.notification import EmailNotificationModel
50 from rhodecode.model.notification import EmailNotificationModel
51 from rhodecode.model.validation_schema.schemas import comment_schema
51 from rhodecode.model.validation_schema.schemas import comment_schema
52
52
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 class CommentsModel(BaseModel):
57 class CommentsModel(BaseModel):
58
58
59 cls = ChangesetComment
59 cls = ChangesetComment
60
60
61 DIFF_CONTEXT_BEFORE = 3
61 DIFF_CONTEXT_BEFORE = 3
62 DIFF_CONTEXT_AFTER = 3
62 DIFF_CONTEXT_AFTER = 3
63
63
64 def __get_commit_comment(self, changeset_comment):
64 def __get_commit_comment(self, changeset_comment):
65 return self._get_instance(ChangesetComment, changeset_comment)
65 return self._get_instance(ChangesetComment, changeset_comment)
66
66
67 def __get_pull_request(self, pull_request):
67 def __get_pull_request(self, pull_request):
68 return self._get_instance(PullRequest, pull_request)
68 return self._get_instance(PullRequest, pull_request)
69
69
70 def _extract_mentions(self, s):
70 def _extract_mentions(self, s):
71 user_objects = []
71 user_objects = []
72 for username in extract_mentioned_users(s):
72 for username in extract_mentioned_users(s):
73 user_obj = User.get_by_username(username, case_insensitive=True)
73 user_obj = User.get_by_username(username, case_insensitive=True)
74 if user_obj:
74 if user_obj:
75 user_objects.append(user_obj)
75 user_objects.append(user_obj)
76 return user_objects
76 return user_objects
77
77
78 def _get_renderer(self, global_renderer='rst', request=None):
78 def _get_renderer(self, global_renderer='rst', request=None):
79 request = request or get_current_request()
79 request = request or get_current_request()
80
80
81 try:
81 try:
82 global_renderer = request.call_context.visual.default_renderer
82 global_renderer = request.call_context.visual.default_renderer
83 except AttributeError:
83 except AttributeError:
84 log.debug("Renderer not set, falling back "
84 log.debug("Renderer not set, falling back "
85 "to default renderer '%s'", global_renderer)
85 "to default renderer '%s'", global_renderer)
86 except Exception:
86 except Exception:
87 log.error(traceback.format_exc())
87 log.error(traceback.format_exc())
88 return global_renderer
88 return global_renderer
89
89
90 def aggregate_comments(self, comments, versions, show_version, inline=False):
90 def aggregate_comments(self, comments, versions, show_version, inline=False):
91 # group by versions, and count until, and display objects
91 # group by versions, and count until, and display objects
92
92
93 comment_groups = collections.defaultdict(list)
93 comment_groups = collections.defaultdict(list)
94 [comment_groups[_co.pull_request_version_id].append(_co) for _co in comments]
94 [comment_groups[_co.pull_request_version_id].append(_co) for _co in comments]
95
95
96 def yield_comments(pos):
96 def yield_comments(pos):
97 for co in comment_groups[pos]:
97 for co in comment_groups[pos]:
98 yield co
98 yield co
99
99
100 comment_versions = collections.defaultdict(
100 comment_versions = collections.defaultdict(
101 lambda: collections.defaultdict(list))
101 lambda: collections.defaultdict(list))
102 prev_prvid = -1
102 prev_prvid = -1
103 # fake last entry with None, to aggregate on "latest" version which
103 # fake last entry with None, to aggregate on "latest" version which
104 # doesn't have an pull_request_version_id
104 # doesn't have an pull_request_version_id
105 for ver in versions + [AttributeDict({'pull_request_version_id': None})]:
105 for ver in versions + [AttributeDict({'pull_request_version_id': None})]:
106 prvid = ver.pull_request_version_id
106 prvid = ver.pull_request_version_id
107 if prev_prvid == -1:
107 if prev_prvid == -1:
108 prev_prvid = prvid
108 prev_prvid = prvid
109
109
110 for co in yield_comments(prvid):
110 for co in yield_comments(prvid):
111 comment_versions[prvid]['at'].append(co)
111 comment_versions[prvid]['at'].append(co)
112
112
113 # save until
113 # save until
114 current = comment_versions[prvid]['at']
114 current = comment_versions[prvid]['at']
115 prev_until = comment_versions[prev_prvid]['until']
115 prev_until = comment_versions[prev_prvid]['until']
116 cur_until = prev_until + current
116 cur_until = prev_until + current
117 comment_versions[prvid]['until'].extend(cur_until)
117 comment_versions[prvid]['until'].extend(cur_until)
118
118
119 # save outdated
119 # save outdated
120 if inline:
120 if inline:
121 outdated = [x for x in cur_until
121 outdated = [x for x in cur_until
122 if x.outdated_at_version(show_version)]
122 if x.outdated_at_version(show_version)]
123 else:
123 else:
124 outdated = [x for x in cur_until
124 outdated = [x for x in cur_until
125 if x.older_than_version(show_version)]
125 if x.older_than_version(show_version)]
126 display = [x for x in cur_until if x not in outdated]
126 display = [x for x in cur_until if x not in outdated]
127
127
128 comment_versions[prvid]['outdated'] = outdated
128 comment_versions[prvid]['outdated'] = outdated
129 comment_versions[prvid]['display'] = display
129 comment_versions[prvid]['display'] = display
130
130
131 prev_prvid = prvid
131 prev_prvid = prvid
132
132
133 return comment_versions
133 return comment_versions
134
134
135 def get_repository_comments(self, repo, comment_type=None, user=None, commit_id=None):
135 def get_repository_comments(self, repo, comment_type=None, user=None, commit_id=None):
136 qry = Session().query(ChangesetComment) \
136 qry = Session().query(ChangesetComment) \
137 .filter(ChangesetComment.repo == repo)
137 .filter(ChangesetComment.repo == repo)
138
138
139 if comment_type and comment_type in ChangesetComment.COMMENT_TYPES:
139 if comment_type and comment_type in ChangesetComment.COMMENT_TYPES:
140 qry = qry.filter(ChangesetComment.comment_type == comment_type)
140 qry = qry.filter(ChangesetComment.comment_type == comment_type)
141
141
142 if user:
142 if user:
143 user = self._get_user(user)
143 user = self._get_user(user)
144 if user:
144 if user:
145 qry = qry.filter(ChangesetComment.user_id == user.user_id)
145 qry = qry.filter(ChangesetComment.user_id == user.user_id)
146
146
147 if commit_id:
147 if commit_id:
148 qry = qry.filter(ChangesetComment.revision == commit_id)
148 qry = qry.filter(ChangesetComment.revision == commit_id)
149
149
150 qry = qry.order_by(ChangesetComment.created_on)
150 qry = qry.order_by(ChangesetComment.created_on)
151 return qry.all()
151 return qry.all()
152
152
153 def get_repository_unresolved_todos(self, repo):
153 def get_repository_unresolved_todos(self, repo):
154 todos = Session().query(ChangesetComment) \
154 todos = Session().query(ChangesetComment) \
155 .filter(ChangesetComment.repo == repo) \
155 .filter(ChangesetComment.repo == repo) \
156 .filter(ChangesetComment.resolved_by == None) \
156 .filter(ChangesetComment.resolved_by == None) \
157 .filter(ChangesetComment.comment_type
157 .filter(ChangesetComment.comment_type
158 == ChangesetComment.COMMENT_TYPE_TODO)
158 == ChangesetComment.COMMENT_TYPE_TODO)
159 todos = todos.all()
159 todos = todos.all()
160
160
161 return todos
161 return todos
162
162
163 def get_pull_request_unresolved_todos(self, pull_request, show_outdated=True):
163 def get_pull_request_unresolved_todos(self, pull_request, show_outdated=True):
164
164
165 todos = Session().query(ChangesetComment) \
165 todos = Session().query(ChangesetComment) \
166 .filter(ChangesetComment.pull_request == pull_request) \
166 .filter(ChangesetComment.pull_request == pull_request) \
167 .filter(ChangesetComment.resolved_by == None) \
167 .filter(ChangesetComment.resolved_by == None) \
168 .filter(ChangesetComment.comment_type
168 .filter(ChangesetComment.comment_type
169 == ChangesetComment.COMMENT_TYPE_TODO)
169 == ChangesetComment.COMMENT_TYPE_TODO)
170
170
171 if not show_outdated:
171 if not show_outdated:
172 todos = todos.filter(
172 todos = todos.filter(
173 coalesce(ChangesetComment.display_state, '') !=
173 coalesce(ChangesetComment.display_state, '') !=
174 ChangesetComment.COMMENT_OUTDATED)
174 ChangesetComment.COMMENT_OUTDATED)
175
175
176 todos = todos.all()
176 todos = todos.all()
177
177
178 return todos
178 return todos
179
179
180 def get_pull_request_resolved_todos(self, pull_request, show_outdated=True):
180 def get_pull_request_resolved_todos(self, pull_request, show_outdated=True):
181
181
182 todos = Session().query(ChangesetComment) \
182 todos = Session().query(ChangesetComment) \
183 .filter(ChangesetComment.pull_request == pull_request) \
183 .filter(ChangesetComment.pull_request == pull_request) \
184 .filter(ChangesetComment.resolved_by != None) \
184 .filter(ChangesetComment.resolved_by != None) \
185 .filter(ChangesetComment.comment_type
185 .filter(ChangesetComment.comment_type
186 == ChangesetComment.COMMENT_TYPE_TODO)
186 == ChangesetComment.COMMENT_TYPE_TODO)
187
187
188 if not show_outdated:
188 if not show_outdated:
189 todos = todos.filter(
189 todos = todos.filter(
190 coalesce(ChangesetComment.display_state, '') !=
190 coalesce(ChangesetComment.display_state, '') !=
191 ChangesetComment.COMMENT_OUTDATED)
191 ChangesetComment.COMMENT_OUTDATED)
192
192
193 todos = todos.all()
193 todos = todos.all()
194
194
195 return todos
195 return todos
196
196
197 def get_commit_unresolved_todos(self, commit_id, show_outdated=True):
197 def get_commit_unresolved_todos(self, commit_id, show_outdated=True):
198
198
199 todos = Session().query(ChangesetComment) \
199 todos = Session().query(ChangesetComment) \
200 .filter(ChangesetComment.revision == commit_id) \
200 .filter(ChangesetComment.revision == commit_id) \
201 .filter(ChangesetComment.resolved_by == None) \
201 .filter(ChangesetComment.resolved_by == None) \
202 .filter(ChangesetComment.comment_type
202 .filter(ChangesetComment.comment_type
203 == ChangesetComment.COMMENT_TYPE_TODO)
203 == ChangesetComment.COMMENT_TYPE_TODO)
204
204
205 if not show_outdated:
205 if not show_outdated:
206 todos = todos.filter(
206 todos = todos.filter(
207 coalesce(ChangesetComment.display_state, '') !=
207 coalesce(ChangesetComment.display_state, '') !=
208 ChangesetComment.COMMENT_OUTDATED)
208 ChangesetComment.COMMENT_OUTDATED)
209
209
210 todos = todos.all()
210 todos = todos.all()
211
211
212 return todos
212 return todos
213
213
214 def get_commit_resolved_todos(self, commit_id, show_outdated=True):
214 def get_commit_resolved_todos(self, commit_id, show_outdated=True):
215
215
216 todos = Session().query(ChangesetComment) \
216 todos = Session().query(ChangesetComment) \
217 .filter(ChangesetComment.revision == commit_id) \
217 .filter(ChangesetComment.revision == commit_id) \
218 .filter(ChangesetComment.resolved_by != None) \
218 .filter(ChangesetComment.resolved_by != None) \
219 .filter(ChangesetComment.comment_type
219 .filter(ChangesetComment.comment_type
220 == ChangesetComment.COMMENT_TYPE_TODO)
220 == ChangesetComment.COMMENT_TYPE_TODO)
221
221
222 if not show_outdated:
222 if not show_outdated:
223 todos = todos.filter(
223 todos = todos.filter(
224 coalesce(ChangesetComment.display_state, '') !=
224 coalesce(ChangesetComment.display_state, '') !=
225 ChangesetComment.COMMENT_OUTDATED)
225 ChangesetComment.COMMENT_OUTDATED)
226
226
227 todos = todos.all()
227 todos = todos.all()
228
228
229 return todos
229 return todos
230
230
231 def get_commit_inline_comments(self, commit_id):
232 inline_comments = Session().query(ChangesetComment) \
233 .filter(ChangesetComment.line_no != None) \
234 .filter(ChangesetComment.f_path != None) \
235 .filter(ChangesetComment.revision == commit_id)
236 inline_comments = inline_comments.all()
237 return inline_comments
238
231 def _log_audit_action(self, action, action_data, auth_user, comment):
239 def _log_audit_action(self, action, action_data, auth_user, comment):
232 audit_logger.store(
240 audit_logger.store(
233 action=action,
241 action=action,
234 action_data=action_data,
242 action_data=action_data,
235 user=auth_user,
243 user=auth_user,
236 repo=comment.repo)
244 repo=comment.repo)
237
245
238 def create(self, text, repo, user, commit_id=None, pull_request=None,
246 def create(self, text, repo, user, commit_id=None, pull_request=None,
239 f_path=None, line_no=None, status_change=None,
247 f_path=None, line_no=None, status_change=None,
240 status_change_type=None, comment_type=None,
248 status_change_type=None, comment_type=None,
241 resolves_comment_id=None, closing_pr=False, send_email=True,
249 resolves_comment_id=None, closing_pr=False, send_email=True,
242 renderer=None, auth_user=None, extra_recipients=None):
250 renderer=None, auth_user=None, extra_recipients=None):
243 """
251 """
244 Creates new comment for commit or pull request.
252 Creates new comment for commit or pull request.
245 IF status_change is not none this comment is associated with a
253 IF status_change is not none this comment is associated with a
246 status change of commit or commit associated with pull request
254 status change of commit or commit associated with pull request
247
255
248 :param text:
256 :param text:
249 :param repo:
257 :param repo:
250 :param user:
258 :param user:
251 :param commit_id:
259 :param commit_id:
252 :param pull_request:
260 :param pull_request:
253 :param f_path:
261 :param f_path:
254 :param line_no:
262 :param line_no:
255 :param status_change: Label for status change
263 :param status_change: Label for status change
256 :param comment_type: Type of comment
264 :param comment_type: Type of comment
257 :param resolves_comment_id: id of comment which this one will resolve
265 :param resolves_comment_id: id of comment which this one will resolve
258 :param status_change_type: type of status change
266 :param status_change_type: type of status change
259 :param closing_pr:
267 :param closing_pr:
260 :param send_email:
268 :param send_email:
261 :param renderer: pick renderer for this comment
269 :param renderer: pick renderer for this comment
262 :param auth_user: current authenticated user calling this method
270 :param auth_user: current authenticated user calling this method
263 :param extra_recipients: list of extra users to be added to recipients
271 :param extra_recipients: list of extra users to be added to recipients
264 """
272 """
265
273
266 if not text:
274 if not text:
267 log.warning('Missing text for comment, skipping...')
275 log.warning('Missing text for comment, skipping...')
268 return
276 return
269 request = get_current_request()
277 request = get_current_request()
270 _ = request.translate
278 _ = request.translate
271
279
272 if not renderer:
280 if not renderer:
273 renderer = self._get_renderer(request=request)
281 renderer = self._get_renderer(request=request)
274
282
275 repo = self._get_repo(repo)
283 repo = self._get_repo(repo)
276 user = self._get_user(user)
284 user = self._get_user(user)
277 auth_user = auth_user or user
285 auth_user = auth_user or user
278
286
279 schema = comment_schema.CommentSchema()
287 schema = comment_schema.CommentSchema()
280 validated_kwargs = schema.deserialize(dict(
288 validated_kwargs = schema.deserialize(dict(
281 comment_body=text,
289 comment_body=text,
282 comment_type=comment_type,
290 comment_type=comment_type,
283 comment_file=f_path,
291 comment_file=f_path,
284 comment_line=line_no,
292 comment_line=line_no,
285 renderer_type=renderer,
293 renderer_type=renderer,
286 status_change=status_change_type,
294 status_change=status_change_type,
287 resolves_comment_id=resolves_comment_id,
295 resolves_comment_id=resolves_comment_id,
288 repo=repo.repo_id,
296 repo=repo.repo_id,
289 user=user.user_id,
297 user=user.user_id,
290 ))
298 ))
291
299
292 comment = ChangesetComment()
300 comment = ChangesetComment()
293 comment.renderer = validated_kwargs['renderer_type']
301 comment.renderer = validated_kwargs['renderer_type']
294 comment.text = validated_kwargs['comment_body']
302 comment.text = validated_kwargs['comment_body']
295 comment.f_path = validated_kwargs['comment_file']
303 comment.f_path = validated_kwargs['comment_file']
296 comment.line_no = validated_kwargs['comment_line']
304 comment.line_no = validated_kwargs['comment_line']
297 comment.comment_type = validated_kwargs['comment_type']
305 comment.comment_type = validated_kwargs['comment_type']
298
306
299 comment.repo = repo
307 comment.repo = repo
300 comment.author = user
308 comment.author = user
301 resolved_comment = self.__get_commit_comment(
309 resolved_comment = self.__get_commit_comment(
302 validated_kwargs['resolves_comment_id'])
310 validated_kwargs['resolves_comment_id'])
303 # check if the comment actually belongs to this PR
311 # check if the comment actually belongs to this PR
304 if resolved_comment and resolved_comment.pull_request and \
312 if resolved_comment and resolved_comment.pull_request and \
305 resolved_comment.pull_request != pull_request:
313 resolved_comment.pull_request != pull_request:
306 log.warning('Comment tried to resolved unrelated todo comment: %s',
314 log.warning('Comment tried to resolved unrelated todo comment: %s',
307 resolved_comment)
315 resolved_comment)
308 # comment not bound to this pull request, forbid
316 # comment not bound to this pull request, forbid
309 resolved_comment = None
317 resolved_comment = None
310
318
311 elif resolved_comment and resolved_comment.repo and \
319 elif resolved_comment and resolved_comment.repo and \
312 resolved_comment.repo != repo:
320 resolved_comment.repo != repo:
313 log.warning('Comment tried to resolved unrelated todo comment: %s',
321 log.warning('Comment tried to resolved unrelated todo comment: %s',
314 resolved_comment)
322 resolved_comment)
315 # comment not bound to this repo, forbid
323 # comment not bound to this repo, forbid
316 resolved_comment = None
324 resolved_comment = None
317
325
318 comment.resolved_comment = resolved_comment
326 comment.resolved_comment = resolved_comment
319
327
320 pull_request_id = pull_request
328 pull_request_id = pull_request
321
329
322 commit_obj = None
330 commit_obj = None
323 pull_request_obj = None
331 pull_request_obj = None
324
332
325 if commit_id:
333 if commit_id:
326 notification_type = EmailNotificationModel.TYPE_COMMIT_COMMENT
334 notification_type = EmailNotificationModel.TYPE_COMMIT_COMMENT
327 # do a lookup, so we don't pass something bad here
335 # do a lookup, so we don't pass something bad here
328 commit_obj = repo.scm_instance().get_commit(commit_id=commit_id)
336 commit_obj = repo.scm_instance().get_commit(commit_id=commit_id)
329 comment.revision = commit_obj.raw_id
337 comment.revision = commit_obj.raw_id
330
338
331 elif pull_request_id:
339 elif pull_request_id:
332 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
340 notification_type = EmailNotificationModel.TYPE_PULL_REQUEST_COMMENT
333 pull_request_obj = self.__get_pull_request(pull_request_id)
341 pull_request_obj = self.__get_pull_request(pull_request_id)
334 comment.pull_request = pull_request_obj
342 comment.pull_request = pull_request_obj
335 else:
343 else:
336 raise Exception('Please specify commit or pull_request_id')
344 raise Exception('Please specify commit or pull_request_id')
337
345
338 Session().add(comment)
346 Session().add(comment)
339 Session().flush()
347 Session().flush()
340 kwargs = {
348 kwargs = {
341 'user': user,
349 'user': user,
342 'renderer_type': renderer,
350 'renderer_type': renderer,
343 'repo_name': repo.repo_name,
351 'repo_name': repo.repo_name,
344 'status_change': status_change,
352 'status_change': status_change,
345 'status_change_type': status_change_type,
353 'status_change_type': status_change_type,
346 'comment_body': text,
354 'comment_body': text,
347 'comment_file': f_path,
355 'comment_file': f_path,
348 'comment_line': line_no,
356 'comment_line': line_no,
349 'comment_type': comment_type or 'note',
357 'comment_type': comment_type or 'note',
350 'comment_id': comment.comment_id
358 'comment_id': comment.comment_id
351 }
359 }
352
360
353 if commit_obj:
361 if commit_obj:
354 recipients = ChangesetComment.get_users(
362 recipients = ChangesetComment.get_users(
355 revision=commit_obj.raw_id)
363 revision=commit_obj.raw_id)
356 # add commit author if it's in RhodeCode system
364 # add commit author if it's in RhodeCode system
357 cs_author = User.get_from_cs_author(commit_obj.author)
365 cs_author = User.get_from_cs_author(commit_obj.author)
358 if not cs_author:
366 if not cs_author:
359 # use repo owner if we cannot extract the author correctly
367 # use repo owner if we cannot extract the author correctly
360 cs_author = repo.user
368 cs_author = repo.user
361 recipients += [cs_author]
369 recipients += [cs_author]
362
370
363 commit_comment_url = self.get_url(comment, request=request)
371 commit_comment_url = self.get_url(comment, request=request)
364 commit_comment_reply_url = self.get_url(
372 commit_comment_reply_url = self.get_url(
365 comment, request=request,
373 comment, request=request,
366 anchor='comment-{}/?/ReplyToComment'.format(comment.comment_id))
374 anchor='comment-{}/?/ReplyToComment'.format(comment.comment_id))
367
375
368 target_repo_url = h.link_to(
376 target_repo_url = h.link_to(
369 repo.repo_name,
377 repo.repo_name,
370 h.route_url('repo_summary', repo_name=repo.repo_name))
378 h.route_url('repo_summary', repo_name=repo.repo_name))
371
379
372 commit_url = h.route_url('repo_commit', repo_name=repo.repo_name,
380 commit_url = h.route_url('repo_commit', repo_name=repo.repo_name,
373 commit_id=commit_id)
381 commit_id=commit_id)
374
382
375 # commit specifics
383 # commit specifics
376 kwargs.update({
384 kwargs.update({
377 'commit': commit_obj,
385 'commit': commit_obj,
378 'commit_message': commit_obj.message,
386 'commit_message': commit_obj.message,
379 'commit_target_repo_url': target_repo_url,
387 'commit_target_repo_url': target_repo_url,
380 'commit_comment_url': commit_comment_url,
388 'commit_comment_url': commit_comment_url,
381 'commit_comment_reply_url': commit_comment_reply_url,
389 'commit_comment_reply_url': commit_comment_reply_url,
382 'commit_url': commit_url,
390 'commit_url': commit_url,
383 'thread_ids': [commit_url, commit_comment_url],
391 'thread_ids': [commit_url, commit_comment_url],
384 })
392 })
385
393
386 elif pull_request_obj:
394 elif pull_request_obj:
387 # get the current participants of this pull request
395 # get the current participants of this pull request
388 recipients = ChangesetComment.get_users(
396 recipients = ChangesetComment.get_users(
389 pull_request_id=pull_request_obj.pull_request_id)
397 pull_request_id=pull_request_obj.pull_request_id)
390 # add pull request author
398 # add pull request author
391 recipients += [pull_request_obj.author]
399 recipients += [pull_request_obj.author]
392
400
393 # add the reviewers to notification
401 # add the reviewers to notification
394 recipients += [x.user for x in pull_request_obj.reviewers]
402 recipients += [x.user for x in pull_request_obj.reviewers]
395
403
396 pr_target_repo = pull_request_obj.target_repo
404 pr_target_repo = pull_request_obj.target_repo
397 pr_source_repo = pull_request_obj.source_repo
405 pr_source_repo = pull_request_obj.source_repo
398
406
399 pr_comment_url = self.get_url(comment, request=request)
407 pr_comment_url = self.get_url(comment, request=request)
400 pr_comment_reply_url = self.get_url(
408 pr_comment_reply_url = self.get_url(
401 comment, request=request,
409 comment, request=request,
402 anchor='comment-{}/?/ReplyToComment'.format(comment.comment_id))
410 anchor='comment-{}/?/ReplyToComment'.format(comment.comment_id))
403
411
404 pr_url = h.route_url(
412 pr_url = h.route_url(
405 'pullrequest_show',
413 'pullrequest_show',
406 repo_name=pr_target_repo.repo_name,
414 repo_name=pr_target_repo.repo_name,
407 pull_request_id=pull_request_obj.pull_request_id, )
415 pull_request_id=pull_request_obj.pull_request_id, )
408
416
409 # set some variables for email notification
417 # set some variables for email notification
410 pr_target_repo_url = h.route_url(
418 pr_target_repo_url = h.route_url(
411 'repo_summary', repo_name=pr_target_repo.repo_name)
419 'repo_summary', repo_name=pr_target_repo.repo_name)
412
420
413 pr_source_repo_url = h.route_url(
421 pr_source_repo_url = h.route_url(
414 'repo_summary', repo_name=pr_source_repo.repo_name)
422 'repo_summary', repo_name=pr_source_repo.repo_name)
415
423
416 # pull request specifics
424 # pull request specifics
417 kwargs.update({
425 kwargs.update({
418 'pull_request': pull_request_obj,
426 'pull_request': pull_request_obj,
419 'pr_id': pull_request_obj.pull_request_id,
427 'pr_id': pull_request_obj.pull_request_id,
420 'pull_request_url': pr_url,
428 'pull_request_url': pr_url,
421 'pull_request_target_repo': pr_target_repo,
429 'pull_request_target_repo': pr_target_repo,
422 'pull_request_target_repo_url': pr_target_repo_url,
430 'pull_request_target_repo_url': pr_target_repo_url,
423 'pull_request_source_repo': pr_source_repo,
431 'pull_request_source_repo': pr_source_repo,
424 'pull_request_source_repo_url': pr_source_repo_url,
432 'pull_request_source_repo_url': pr_source_repo_url,
425 'pr_comment_url': pr_comment_url,
433 'pr_comment_url': pr_comment_url,
426 'pr_comment_reply_url': pr_comment_reply_url,
434 'pr_comment_reply_url': pr_comment_reply_url,
427 'pr_closing': closing_pr,
435 'pr_closing': closing_pr,
428 'thread_ids': [pr_url, pr_comment_url],
436 'thread_ids': [pr_url, pr_comment_url],
429 })
437 })
430
438
431 recipients += [self._get_user(u) for u in (extra_recipients or [])]
439 recipients += [self._get_user(u) for u in (extra_recipients or [])]
432
440
433 if send_email:
441 if send_email:
434 # pre-generate the subject for notification itself
442 # pre-generate the subject for notification itself
435 (subject, _e, body_plaintext) = EmailNotificationModel().render_email(
443 (subject, _e, body_plaintext) = EmailNotificationModel().render_email(
436 notification_type, **kwargs)
444 notification_type, **kwargs)
437
445
438 mention_recipients = set(
446 mention_recipients = set(
439 self._extract_mentions(text)).difference(recipients)
447 self._extract_mentions(text)).difference(recipients)
440
448
441 # create notification objects, and emails
449 # create notification objects, and emails
442 NotificationModel().create(
450 NotificationModel().create(
443 created_by=user,
451 created_by=user,
444 notification_subject=subject,
452 notification_subject=subject,
445 notification_body=body_plaintext,
453 notification_body=body_plaintext,
446 notification_type=notification_type,
454 notification_type=notification_type,
447 recipients=recipients,
455 recipients=recipients,
448 mention_recipients=mention_recipients,
456 mention_recipients=mention_recipients,
449 email_kwargs=kwargs,
457 email_kwargs=kwargs,
450 )
458 )
451
459
452 Session().flush()
460 Session().flush()
453 if comment.pull_request:
461 if comment.pull_request:
454 action = 'repo.pull_request.comment.create'
462 action = 'repo.pull_request.comment.create'
455 else:
463 else:
456 action = 'repo.commit.comment.create'
464 action = 'repo.commit.comment.create'
457
465
458 comment_id = comment.comment_id
466 comment_id = comment.comment_id
459 comment_data = comment.get_api_data()
467 comment_data = comment.get_api_data()
460
468
461 self._log_audit_action(
469 self._log_audit_action(
462 action, {'data': comment_data}, auth_user, comment)
470 action, {'data': comment_data}, auth_user, comment)
463
471
464 channel = None
472 channel = None
465 if commit_obj:
473 if commit_obj:
466 repo_name = repo.repo_name
474 repo_name = repo.repo_name
467 channel = u'/repo${}$/commit/{}'.format(
475 channel = u'/repo${}$/commit/{}'.format(
468 repo_name,
476 repo_name,
469 commit_obj.raw_id
477 commit_obj.raw_id
470 )
478 )
471 elif pull_request_obj:
479 elif pull_request_obj:
472 repo_name = pr_target_repo.repo_name
480 repo_name = pr_target_repo.repo_name
473 channel = u'/repo${}$/pr/{}'.format(
481 channel = u'/repo${}$/pr/{}'.format(
474 repo_name,
482 repo_name,
475 pull_request_obj.pull_request_id
483 pull_request_obj.pull_request_id
476 )
484 )
477
485
478 if channel:
486 if channel:
479 username = user.username
487 username = user.username
480 message = '<strong>{}</strong> {} #{}, {}'
488 message = '<strong>{}</strong> {} #{}, {}'
481 message = message.format(
489 message = message.format(
482 username,
490 username,
483 _('posted a new comment'),
491 _('posted a new comment'),
484 comment_id,
492 comment_id,
485 _('Refresh the page to see new comments.'))
493 _('Refresh the page to see new comments.'))
486
494
487 message_obj = {
495 message_obj = {
488 'message': message,
496 'message': message,
489 'level': 'success',
497 'level': 'success',
490 'topic': '/notifications'
498 'topic': '/notifications'
491 }
499 }
492
500
493 channelstream.post_message(
501 channelstream.post_message(
494 channel, message_obj, user.username,
502 channel, message_obj, user.username,
495 registry=get_current_registry())
503 registry=get_current_registry())
496
504
497 message_obj = {
505 message_obj = {
498 'message': None,
506 'message': None,
499 'user': username,
507 'user': username,
500 'comment_id': comment_id,
508 'comment_id': comment_id,
501 'topic': '/comment'
509 'topic': '/comment'
502 }
510 }
503 channelstream.post_message(
511 channelstream.post_message(
504 channel, message_obj, user.username,
512 channel, message_obj, user.username,
505 registry=get_current_registry())
513 registry=get_current_registry())
506
514
507 return comment
515 return comment
508
516
509 def edit(self, comment_id, text, auth_user, version):
517 def edit(self, comment_id, text, auth_user, version):
510 """
518 """
511 Change existing comment for commit or pull request.
519 Change existing comment for commit or pull request.
512
520
513 :param comment_id:
521 :param comment_id:
514 :param text:
522 :param text:
515 :param auth_user: current authenticated user calling this method
523 :param auth_user: current authenticated user calling this method
516 :param version: last comment version
524 :param version: last comment version
517 """
525 """
518 if not text:
526 if not text:
519 log.warning('Missing text for comment, skipping...')
527 log.warning('Missing text for comment, skipping...')
520 return
528 return
521
529
522 comment = ChangesetComment.get(comment_id)
530 comment = ChangesetComment.get(comment_id)
523 old_comment_text = comment.text
531 old_comment_text = comment.text
524 comment.text = text
532 comment.text = text
525 comment.modified_at = datetime.datetime.now()
533 comment.modified_at = datetime.datetime.now()
526 version = safe_int(version)
534 version = safe_int(version)
527
535
528 # NOTE(marcink): this returns initial comment + edits, so v2 from ui
536 # NOTE(marcink): this returns initial comment + edits, so v2 from ui
529 # would return 3 here
537 # would return 3 here
530 comment_version = ChangesetCommentHistory.get_version(comment_id)
538 comment_version = ChangesetCommentHistory.get_version(comment_id)
531
539
532 if isinstance(version, (int, long)) and (comment_version - version) != 1:
540 if isinstance(version, (int, long)) and (comment_version - version) != 1:
533 log.warning(
541 log.warning(
534 'Version mismatch comment_version {} submitted {}, skipping'.format(
542 'Version mismatch comment_version {} submitted {}, skipping'.format(
535 comment_version-1, # -1 since note above
543 comment_version-1, # -1 since note above
536 version
544 version
537 )
545 )
538 )
546 )
539 raise CommentVersionMismatch()
547 raise CommentVersionMismatch()
540
548
541 comment_history = ChangesetCommentHistory()
549 comment_history = ChangesetCommentHistory()
542 comment_history.comment_id = comment_id
550 comment_history.comment_id = comment_id
543 comment_history.version = comment_version
551 comment_history.version = comment_version
544 comment_history.created_by_user_id = auth_user.user_id
552 comment_history.created_by_user_id = auth_user.user_id
545 comment_history.text = old_comment_text
553 comment_history.text = old_comment_text
546 # TODO add email notification
554 # TODO add email notification
547 Session().add(comment_history)
555 Session().add(comment_history)
548 Session().add(comment)
556 Session().add(comment)
549 Session().flush()
557 Session().flush()
550
558
551 if comment.pull_request:
559 if comment.pull_request:
552 action = 'repo.pull_request.comment.edit'
560 action = 'repo.pull_request.comment.edit'
553 else:
561 else:
554 action = 'repo.commit.comment.edit'
562 action = 'repo.commit.comment.edit'
555
563
556 comment_data = comment.get_api_data()
564 comment_data = comment.get_api_data()
557 comment_data['old_comment_text'] = old_comment_text
565 comment_data['old_comment_text'] = old_comment_text
558 self._log_audit_action(
566 self._log_audit_action(
559 action, {'data': comment_data}, auth_user, comment)
567 action, {'data': comment_data}, auth_user, comment)
560
568
561 return comment_history
569 return comment_history
562
570
563 def delete(self, comment, auth_user):
571 def delete(self, comment, auth_user):
564 """
572 """
565 Deletes given comment
573 Deletes given comment
566 """
574 """
567 comment = self.__get_commit_comment(comment)
575 comment = self.__get_commit_comment(comment)
568 old_data = comment.get_api_data()
576 old_data = comment.get_api_data()
569 Session().delete(comment)
577 Session().delete(comment)
570
578
571 if comment.pull_request:
579 if comment.pull_request:
572 action = 'repo.pull_request.comment.delete'
580 action = 'repo.pull_request.comment.delete'
573 else:
581 else:
574 action = 'repo.commit.comment.delete'
582 action = 'repo.commit.comment.delete'
575
583
576 self._log_audit_action(
584 self._log_audit_action(
577 action, {'old_data': old_data}, auth_user, comment)
585 action, {'old_data': old_data}, auth_user, comment)
578
586
579 return comment
587 return comment
580
588
581 def get_all_comments(self, repo_id, revision=None, pull_request=None):
589 def get_all_comments(self, repo_id, revision=None, pull_request=None):
582 q = ChangesetComment.query()\
590 q = ChangesetComment.query()\
583 .filter(ChangesetComment.repo_id == repo_id)
591 .filter(ChangesetComment.repo_id == repo_id)
584 if revision:
592 if revision:
585 q = q.filter(ChangesetComment.revision == revision)
593 q = q.filter(ChangesetComment.revision == revision)
586 elif pull_request:
594 elif pull_request:
587 pull_request = self.__get_pull_request(pull_request)
595 pull_request = self.__get_pull_request(pull_request)
588 q = q.filter(ChangesetComment.pull_request == pull_request)
596 q = q.filter(ChangesetComment.pull_request == pull_request)
589 else:
597 else:
590 raise Exception('Please specify commit or pull_request')
598 raise Exception('Please specify commit or pull_request')
591 q = q.order_by(ChangesetComment.created_on)
599 q = q.order_by(ChangesetComment.created_on)
592 return q.all()
600 return q.all()
593
601
594 def get_url(self, comment, request=None, permalink=False, anchor=None):
602 def get_url(self, comment, request=None, permalink=False, anchor=None):
595 if not request:
603 if not request:
596 request = get_current_request()
604 request = get_current_request()
597
605
598 comment = self.__get_commit_comment(comment)
606 comment = self.__get_commit_comment(comment)
599 if anchor is None:
607 if anchor is None:
600 anchor = 'comment-{}'.format(comment.comment_id)
608 anchor = 'comment-{}'.format(comment.comment_id)
601
609
602 if comment.pull_request:
610 if comment.pull_request:
603 pull_request = comment.pull_request
611 pull_request = comment.pull_request
604 if permalink:
612 if permalink:
605 return request.route_url(
613 return request.route_url(
606 'pull_requests_global',
614 'pull_requests_global',
607 pull_request_id=pull_request.pull_request_id,
615 pull_request_id=pull_request.pull_request_id,
608 _anchor=anchor)
616 _anchor=anchor)
609 else:
617 else:
610 return request.route_url(
618 return request.route_url(
611 'pullrequest_show',
619 'pullrequest_show',
612 repo_name=safe_str(pull_request.target_repo.repo_name),
620 repo_name=safe_str(pull_request.target_repo.repo_name),
613 pull_request_id=pull_request.pull_request_id,
621 pull_request_id=pull_request.pull_request_id,
614 _anchor=anchor)
622 _anchor=anchor)
615
623
616 else:
624 else:
617 repo = comment.repo
625 repo = comment.repo
618 commit_id = comment.revision
626 commit_id = comment.revision
619
627
620 if permalink:
628 if permalink:
621 return request.route_url(
629 return request.route_url(
622 'repo_commit', repo_name=safe_str(repo.repo_id),
630 'repo_commit', repo_name=safe_str(repo.repo_id),
623 commit_id=commit_id,
631 commit_id=commit_id,
624 _anchor=anchor)
632 _anchor=anchor)
625
633
626 else:
634 else:
627 return request.route_url(
635 return request.route_url(
628 'repo_commit', repo_name=safe_str(repo.repo_name),
636 'repo_commit', repo_name=safe_str(repo.repo_name),
629 commit_id=commit_id,
637 commit_id=commit_id,
630 _anchor=anchor)
638 _anchor=anchor)
631
639
632 def get_comments(self, repo_id, revision=None, pull_request=None):
640 def get_comments(self, repo_id, revision=None, pull_request=None):
633 """
641 """
634 Gets main comments based on revision or pull_request_id
642 Gets main comments based on revision or pull_request_id
635
643
636 :param repo_id:
644 :param repo_id:
637 :param revision:
645 :param revision:
638 :param pull_request:
646 :param pull_request:
639 """
647 """
640
648
641 q = ChangesetComment.query()\
649 q = ChangesetComment.query()\
642 .filter(ChangesetComment.repo_id == repo_id)\
650 .filter(ChangesetComment.repo_id == repo_id)\
643 .filter(ChangesetComment.line_no == None)\
651 .filter(ChangesetComment.line_no == None)\
644 .filter(ChangesetComment.f_path == None)
652 .filter(ChangesetComment.f_path == None)
645 if revision:
653 if revision:
646 q = q.filter(ChangesetComment.revision == revision)
654 q = q.filter(ChangesetComment.revision == revision)
647 elif pull_request:
655 elif pull_request:
648 pull_request = self.__get_pull_request(pull_request)
656 pull_request = self.__get_pull_request(pull_request)
649 q = q.filter(ChangesetComment.pull_request == pull_request)
657 q = q.filter(ChangesetComment.pull_request == pull_request)
650 else:
658 else:
651 raise Exception('Please specify commit or pull_request')
659 raise Exception('Please specify commit or pull_request')
652 q = q.order_by(ChangesetComment.created_on)
660 q = q.order_by(ChangesetComment.created_on)
653 return q.all()
661 return q.all()
654
662
655 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
663 def get_inline_comments(self, repo_id, revision=None, pull_request=None):
656 q = self._get_inline_comments_query(repo_id, revision, pull_request)
664 q = self._get_inline_comments_query(repo_id, revision, pull_request)
657 return self._group_comments_by_path_and_line_number(q)
665 return self._group_comments_by_path_and_line_number(q)
658
666
659 def get_inline_comments_as_list(self, inline_comments, skip_outdated=True,
667 def get_inline_comments_as_list(self, inline_comments, skip_outdated=True,
660 version=None):
668 version=None):
661 inline_comms = []
669 inline_comms = []
662 for fname, per_line_comments in inline_comments.iteritems():
670 for fname, per_line_comments in inline_comments.iteritems():
663 for lno, comments in per_line_comments.iteritems():
671 for lno, comments in per_line_comments.iteritems():
664 for comm in comments:
672 for comm in comments:
665 if not comm.outdated_at_version(version) and skip_outdated:
673 if not comm.outdated_at_version(version) and skip_outdated:
666 inline_comms.append(comm)
674 inline_comms.append(comm)
667
675
668 return inline_comms
676 return inline_comms
669
677
670 def get_outdated_comments(self, repo_id, pull_request):
678 def get_outdated_comments(self, repo_id, pull_request):
671 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
679 # TODO: johbo: Remove `repo_id`, it is not needed to find the comments
672 # of a pull request.
680 # of a pull request.
673 q = self._all_inline_comments_of_pull_request(pull_request)
681 q = self._all_inline_comments_of_pull_request(pull_request)
674 q = q.filter(
682 q = q.filter(
675 ChangesetComment.display_state ==
683 ChangesetComment.display_state ==
676 ChangesetComment.COMMENT_OUTDATED
684 ChangesetComment.COMMENT_OUTDATED
677 ).order_by(ChangesetComment.comment_id.asc())
685 ).order_by(ChangesetComment.comment_id.asc())
678
686
679 return self._group_comments_by_path_and_line_number(q)
687 return self._group_comments_by_path_and_line_number(q)
680
688
681 def _get_inline_comments_query(self, repo_id, revision, pull_request):
689 def _get_inline_comments_query(self, repo_id, revision, pull_request):
682 # TODO: johbo: Split this into two methods: One for PR and one for
690 # TODO: johbo: Split this into two methods: One for PR and one for
683 # commit.
691 # commit.
684 if revision:
692 if revision:
685 q = Session().query(ChangesetComment).filter(
693 q = Session().query(ChangesetComment).filter(
686 ChangesetComment.repo_id == repo_id,
694 ChangesetComment.repo_id == repo_id,
687 ChangesetComment.line_no != null(),
695 ChangesetComment.line_no != null(),
688 ChangesetComment.f_path != null(),
696 ChangesetComment.f_path != null(),
689 ChangesetComment.revision == revision)
697 ChangesetComment.revision == revision)
690
698
691 elif pull_request:
699 elif pull_request:
692 pull_request = self.__get_pull_request(pull_request)
700 pull_request = self.__get_pull_request(pull_request)
693 if not CommentsModel.use_outdated_comments(pull_request):
701 if not CommentsModel.use_outdated_comments(pull_request):
694 q = self._visible_inline_comments_of_pull_request(pull_request)
702 q = self._visible_inline_comments_of_pull_request(pull_request)
695 else:
703 else:
696 q = self._all_inline_comments_of_pull_request(pull_request)
704 q = self._all_inline_comments_of_pull_request(pull_request)
697
705
698 else:
706 else:
699 raise Exception('Please specify commit or pull_request_id')
707 raise Exception('Please specify commit or pull_request_id')
700 q = q.order_by(ChangesetComment.comment_id.asc())
708 q = q.order_by(ChangesetComment.comment_id.asc())
701 return q
709 return q
702
710
703 def _group_comments_by_path_and_line_number(self, q):
711 def _group_comments_by_path_and_line_number(self, q):
704 comments = q.all()
712 comments = q.all()
705 paths = collections.defaultdict(lambda: collections.defaultdict(list))
713 paths = collections.defaultdict(lambda: collections.defaultdict(list))
706 for co in comments:
714 for co in comments:
707 paths[co.f_path][co.line_no].append(co)
715 paths[co.f_path][co.line_no].append(co)
708 return paths
716 return paths
709
717
710 @classmethod
718 @classmethod
711 def needed_extra_diff_context(cls):
719 def needed_extra_diff_context(cls):
712 return max(cls.DIFF_CONTEXT_BEFORE, cls.DIFF_CONTEXT_AFTER)
720 return max(cls.DIFF_CONTEXT_BEFORE, cls.DIFF_CONTEXT_AFTER)
713
721
714 def outdate_comments(self, pull_request, old_diff_data, new_diff_data):
722 def outdate_comments(self, pull_request, old_diff_data, new_diff_data):
715 if not CommentsModel.use_outdated_comments(pull_request):
723 if not CommentsModel.use_outdated_comments(pull_request):
716 return
724 return
717
725
718 comments = self._visible_inline_comments_of_pull_request(pull_request)
726 comments = self._visible_inline_comments_of_pull_request(pull_request)
719 comments_to_outdate = comments.all()
727 comments_to_outdate = comments.all()
720
728
721 for comment in comments_to_outdate:
729 for comment in comments_to_outdate:
722 self._outdate_one_comment(comment, old_diff_data, new_diff_data)
730 self._outdate_one_comment(comment, old_diff_data, new_diff_data)
723
731
724 def _outdate_one_comment(self, comment, old_diff_proc, new_diff_proc):
732 def _outdate_one_comment(self, comment, old_diff_proc, new_diff_proc):
725 diff_line = _parse_comment_line_number(comment.line_no)
733 diff_line = _parse_comment_line_number(comment.line_no)
726
734
727 try:
735 try:
728 old_context = old_diff_proc.get_context_of_line(
736 old_context = old_diff_proc.get_context_of_line(
729 path=comment.f_path, diff_line=diff_line)
737 path=comment.f_path, diff_line=diff_line)
730 new_context = new_diff_proc.get_context_of_line(
738 new_context = new_diff_proc.get_context_of_line(
731 path=comment.f_path, diff_line=diff_line)
739 path=comment.f_path, diff_line=diff_line)
732 except (diffs.LineNotInDiffException,
740 except (diffs.LineNotInDiffException,
733 diffs.FileNotInDiffException):
741 diffs.FileNotInDiffException):
734 comment.display_state = ChangesetComment.COMMENT_OUTDATED
742 comment.display_state = ChangesetComment.COMMENT_OUTDATED
735 return
743 return
736
744
737 if old_context == new_context:
745 if old_context == new_context:
738 return
746 return
739
747
740 if self._should_relocate_diff_line(diff_line):
748 if self._should_relocate_diff_line(diff_line):
741 new_diff_lines = new_diff_proc.find_context(
749 new_diff_lines = new_diff_proc.find_context(
742 path=comment.f_path, context=old_context,
750 path=comment.f_path, context=old_context,
743 offset=self.DIFF_CONTEXT_BEFORE)
751 offset=self.DIFF_CONTEXT_BEFORE)
744 if not new_diff_lines:
752 if not new_diff_lines:
745 comment.display_state = ChangesetComment.COMMENT_OUTDATED
753 comment.display_state = ChangesetComment.COMMENT_OUTDATED
746 else:
754 else:
747 new_diff_line = self._choose_closest_diff_line(
755 new_diff_line = self._choose_closest_diff_line(
748 diff_line, new_diff_lines)
756 diff_line, new_diff_lines)
749 comment.line_no = _diff_to_comment_line_number(new_diff_line)
757 comment.line_no = _diff_to_comment_line_number(new_diff_line)
750 else:
758 else:
751 comment.display_state = ChangesetComment.COMMENT_OUTDATED
759 comment.display_state = ChangesetComment.COMMENT_OUTDATED
752
760
753 def _should_relocate_diff_line(self, diff_line):
761 def _should_relocate_diff_line(self, diff_line):
754 """
762 """
755 Checks if relocation shall be tried for the given `diff_line`.
763 Checks if relocation shall be tried for the given `diff_line`.
756
764
757 If a comment points into the first lines, then we can have a situation
765 If a comment points into the first lines, then we can have a situation
758 that after an update another line has been added on top. In this case
766 that after an update another line has been added on top. In this case
759 we would find the context still and move the comment around. This
767 we would find the context still and move the comment around. This
760 would be wrong.
768 would be wrong.
761 """
769 """
762 should_relocate = (
770 should_relocate = (
763 (diff_line.new and diff_line.new > self.DIFF_CONTEXT_BEFORE) or
771 (diff_line.new and diff_line.new > self.DIFF_CONTEXT_BEFORE) or
764 (diff_line.old and diff_line.old > self.DIFF_CONTEXT_BEFORE))
772 (diff_line.old and diff_line.old > self.DIFF_CONTEXT_BEFORE))
765 return should_relocate
773 return should_relocate
766
774
767 def _choose_closest_diff_line(self, diff_line, new_diff_lines):
775 def _choose_closest_diff_line(self, diff_line, new_diff_lines):
768 candidate = new_diff_lines[0]
776 candidate = new_diff_lines[0]
769 best_delta = _diff_line_delta(diff_line, candidate)
777 best_delta = _diff_line_delta(diff_line, candidate)
770 for new_diff_line in new_diff_lines[1:]:
778 for new_diff_line in new_diff_lines[1:]:
771 delta = _diff_line_delta(diff_line, new_diff_line)
779 delta = _diff_line_delta(diff_line, new_diff_line)
772 if delta < best_delta:
780 if delta < best_delta:
773 candidate = new_diff_line
781 candidate = new_diff_line
774 best_delta = delta
782 best_delta = delta
775 return candidate
783 return candidate
776
784
777 def _visible_inline_comments_of_pull_request(self, pull_request):
785 def _visible_inline_comments_of_pull_request(self, pull_request):
778 comments = self._all_inline_comments_of_pull_request(pull_request)
786 comments = self._all_inline_comments_of_pull_request(pull_request)
779 comments = comments.filter(
787 comments = comments.filter(
780 coalesce(ChangesetComment.display_state, '') !=
788 coalesce(ChangesetComment.display_state, '') !=
781 ChangesetComment.COMMENT_OUTDATED)
789 ChangesetComment.COMMENT_OUTDATED)
782 return comments
790 return comments
783
791
784 def _all_inline_comments_of_pull_request(self, pull_request):
792 def _all_inline_comments_of_pull_request(self, pull_request):
785 comments = Session().query(ChangesetComment)\
793 comments = Session().query(ChangesetComment)\
786 .filter(ChangesetComment.line_no != None)\
794 .filter(ChangesetComment.line_no != None)\
787 .filter(ChangesetComment.f_path != None)\
795 .filter(ChangesetComment.f_path != None)\
788 .filter(ChangesetComment.pull_request == pull_request)
796 .filter(ChangesetComment.pull_request == pull_request)
789 return comments
797 return comments
790
798
791 def _all_general_comments_of_pull_request(self, pull_request):
799 def _all_general_comments_of_pull_request(self, pull_request):
792 comments = Session().query(ChangesetComment)\
800 comments = Session().query(ChangesetComment)\
793 .filter(ChangesetComment.line_no == None)\
801 .filter(ChangesetComment.line_no == None)\
794 .filter(ChangesetComment.f_path == None)\
802 .filter(ChangesetComment.f_path == None)\
795 .filter(ChangesetComment.pull_request == pull_request)
803 .filter(ChangesetComment.pull_request == pull_request)
796
804
797 return comments
805 return comments
798
806
799 @staticmethod
807 @staticmethod
800 def use_outdated_comments(pull_request):
808 def use_outdated_comments(pull_request):
801 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
809 settings_model = VcsSettingsModel(repo=pull_request.target_repo)
802 settings = settings_model.get_general_settings()
810 settings = settings_model.get_general_settings()
803 return settings.get('rhodecode_use_outdated_comments', False)
811 return settings.get('rhodecode_use_outdated_comments', False)
804
812
805 def trigger_commit_comment_hook(self, repo, user, action, data=None):
813 def trigger_commit_comment_hook(self, repo, user, action, data=None):
806 repo = self._get_repo(repo)
814 repo = self._get_repo(repo)
807 target_scm = repo.scm_instance()
815 target_scm = repo.scm_instance()
808 if action == 'create':
816 if action == 'create':
809 trigger_hook = hooks_utils.trigger_comment_commit_hooks
817 trigger_hook = hooks_utils.trigger_comment_commit_hooks
810 elif action == 'edit':
818 elif action == 'edit':
811 trigger_hook = hooks_utils.trigger_comment_commit_edit_hooks
819 trigger_hook = hooks_utils.trigger_comment_commit_edit_hooks
812 else:
820 else:
813 return
821 return
814
822
815 log.debug('Handling repo %s trigger_commit_comment_hook with action %s: %s',
823 log.debug('Handling repo %s trigger_commit_comment_hook with action %s: %s',
816 repo, action, trigger_hook)
824 repo, action, trigger_hook)
817 trigger_hook(
825 trigger_hook(
818 username=user.username,
826 username=user.username,
819 repo_name=repo.repo_name,
827 repo_name=repo.repo_name,
820 repo_type=target_scm.alias,
828 repo_type=target_scm.alias,
821 repo=repo,
829 repo=repo,
822 data=data)
830 data=data)
823
831
824
832
825 def _parse_comment_line_number(line_no):
833 def _parse_comment_line_number(line_no):
826 """
834 """
827 Parses line numbers of the form "(o|n)\d+" and returns them in a tuple.
835 Parses line numbers of the form "(o|n)\d+" and returns them in a tuple.
828 """
836 """
829 old_line = None
837 old_line = None
830 new_line = None
838 new_line = None
831 if line_no.startswith('o'):
839 if line_no.startswith('o'):
832 old_line = int(line_no[1:])
840 old_line = int(line_no[1:])
833 elif line_no.startswith('n'):
841 elif line_no.startswith('n'):
834 new_line = int(line_no[1:])
842 new_line = int(line_no[1:])
835 else:
843 else:
836 raise ValueError("Comment lines have to start with either 'o' or 'n'.")
844 raise ValueError("Comment lines have to start with either 'o' or 'n'.")
837 return diffs.DiffLineNumber(old_line, new_line)
845 return diffs.DiffLineNumber(old_line, new_line)
838
846
839
847
840 def _diff_to_comment_line_number(diff_line):
848 def _diff_to_comment_line_number(diff_line):
841 if diff_line.new is not None:
849 if diff_line.new is not None:
842 return u'n{}'.format(diff_line.new)
850 return u'n{}'.format(diff_line.new)
843 elif diff_line.old is not None:
851 elif diff_line.old is not None:
844 return u'o{}'.format(diff_line.old)
852 return u'o{}'.format(diff_line.old)
845 return u''
853 return u''
846
854
847
855
848 def _diff_line_delta(a, b):
856 def _diff_line_delta(a, b):
849 if None not in (a.new, b.new):
857 if None not in (a.new, b.new):
850 return abs(a.new - b.new)
858 return abs(a.new - b.new)
851 elif None not in (a.old, b.old):
859 elif None not in (a.old, b.old):
852 return abs(a.old - b.old)
860 return abs(a.old - b.old)
853 else:
861 else:
854 raise ValueError(
862 raise ValueError(
855 "Cannot compute delta between {} and {}".format(a, b))
863 "Cannot compute delta between {} and {}".format(a, b))
@@ -1,57 +1,70 b''
1 .alert1 { .border ( @border-thickness-tags, @alert1 ); color:@alert1; }
1 .alert1 { .border ( @border-thickness-tags, @alert1 ); color:@alert1; }
2 .alert2 { .border ( @border-thickness-tags, @alert2 ); color:@alert2; }
2 .alert2 { .border ( @border-thickness-tags, @alert2 ); color:@alert2; }
3 .alert3 { .border ( @border-thickness-tags, @alert3 ); color:@alert3; }
3 .alert3 { .border ( @border-thickness-tags, @alert3 ); color:@alert3; }
4 .alert4 { .border ( @border-thickness-tags, @alert4 ); color:@alert4; }
4 .alert4 { .border ( @border-thickness-tags, @alert4 ); color:@alert4; }
5
5
6 .alert {
6 .alert {
7 clear: both;
7 clear: both;
8 padding: @padding;
8 padding: @padding;
9 border: @border-thickness solid;
9 border: @border-thickness solid;
10 border-radius: @border-radius;
10 border-radius: @border-radius;
11
11
12 // overwritter css from specific alerts
12 // overwritter css from specific alerts
13 color: @grey3;
13 color: @grey3;
14 border-color: @alert4;
14 border-color: @alert4;
15 background-color: @alert4-inner;
15 background-color: @alert4-inner;
16
16
17 a {
17 a {
18 text-decoration: underline;
18 text-decoration: underline;
19 }
19 }
20 .close {
20 .close {
21 color: @grey3;
21 color: @grey3;
22 }
22 }
23 }
23 }
24
24
25 .infoform .alert {
25 .infoform .alert {
26 width: 100%;
26 width: 100%;
27 margin-top: 0;
27 margin-top: 0;
28 }
28 }
29
29
30 .alert-success {
30 .alert-success {
31 border-color: @alert1;
31 border-color: @alert1;
32 background-color: @alert1-inner;
32 background-color: @alert1-inner;
33 }
33 }
34
34
35 .alert-error {
35 .alert-error {
36 border-color: @alert2;
36 border-color: @alert2;
37 background-color: @alert2-inner;
37 background-color: @alert2-inner;
38 }
38 }
39
39
40 .alert-warning {
40 .alert-warning {
41 border-color: @alert3;
41 border-color: @alert3;
42 background-color: @alert3-inner;
42 background-color: @alert3-inner;
43 }
43 }
44
44
45 .alert-dismissable {
45 .alert-dismissable {
46 padding-right: 10px;
46 padding-right: 10px;
47
47
48 .close {
48 .close {
49 margin-top: -5px;
49 margin-top: -5px;
50 }
50 }
51 }
51 }
52
52
53 .loginbox {
53 .loginbox {
54 .alert {
54 .alert {
55 margin: 0 auto 35px auto;
55 margin: 0 auto 35px auto;
56 }
56 }
57 }
57 }
58
59 .alert-text-success {
60 color: @alert1;
61
62 }
63
64 .alert-text-error {
65 color: @alert2;
66 }
67
68 .alert-text-warning {
69 color: @alert3;
70 }
@@ -1,541 +1,541 b''
1
1
2
2
3 //BUTTONS
3 //BUTTONS
4 button,
4 button,
5 .btn,
5 .btn,
6 input[type="button"] {
6 input[type="button"] {
7 -webkit-appearance: none;
7 -webkit-appearance: none;
8 display: inline-block;
8 display: inline-block;
9 margin: 0 @padding/3 0 0;
9 margin: 0 @padding/3 0 0;
10 padding: @button-padding;
10 padding: @button-padding;
11 text-align: center;
11 text-align: center;
12 font-size: @basefontsize;
12 font-size: @basefontsize;
13 line-height: 1em;
13 line-height: 1em;
14 font-family: @text-light;
14 font-family: @text-light;
15 text-decoration: none;
15 text-decoration: none;
16 text-shadow: none;
16 text-shadow: none;
17 color: @grey2;
17 color: @grey2;
18 background-color: white;
18 background-color: white;
19 background-image: none;
19 background-image: none;
20 border: none;
20 border: none;
21 .border ( @border-thickness-buttons, @grey5 );
21 .border ( @border-thickness-buttons, @grey5 );
22 .border-radius (@border-radius);
22 .border-radius (@border-radius);
23 cursor: pointer;
23 cursor: pointer;
24 white-space: nowrap;
24 white-space: nowrap;
25 -webkit-transition: background .3s,color .3s;
25 -webkit-transition: background .3s,color .3s;
26 -moz-transition: background .3s,color .3s;
26 -moz-transition: background .3s,color .3s;
27 -o-transition: background .3s,color .3s;
27 -o-transition: background .3s,color .3s;
28 transition: background .3s,color .3s;
28 transition: background .3s,color .3s;
29 box-shadow: @button-shadow;
29 box-shadow: @button-shadow;
30 -webkit-box-shadow: @button-shadow;
30 -webkit-box-shadow: @button-shadow;
31
31
32
32
33
33
34 a {
34 a {
35 display: block;
35 display: block;
36 margin: 0;
36 margin: 0;
37 padding: 0;
37 padding: 0;
38 color: inherit;
38 color: inherit;
39 text-decoration: none;
39 text-decoration: none;
40
40
41 &:hover {
41 &:hover {
42 text-decoration: none;
42 text-decoration: none;
43 }
43 }
44 }
44 }
45
45
46 &:focus,
46 &:focus,
47 &:active {
47 &:active {
48 outline:none;
48 outline:none;
49 }
49 }
50
50
51 &:hover {
51 &:hover {
52 color: @rcdarkblue;
52 color: @rcdarkblue;
53 background-color: @grey6;
53 background-color: @grey6;
54
54
55 }
55 }
56
56
57 &.btn-active {
57 &.btn-active {
58 color: @rcdarkblue;
58 color: @rcdarkblue;
59 background-color: @grey6;
59 background-color: @grey6;
60 }
60 }
61
61
62 .icon-remove {
62 .icon-remove {
63 display: none;
63 display: none;
64 }
64 }
65
65
66 //disabled buttons
66 //disabled buttons
67 //last; overrides any other styles
67 //last; overrides any other styles
68 &:disabled {
68 &:disabled {
69 opacity: .7;
69 opacity: .7;
70 cursor: auto;
70 cursor: auto;
71 background-color: white;
71 background-color: white;
72 color: @grey4;
72 color: @grey4;
73 text-shadow: none;
73 text-shadow: none;
74 }
74 }
75
75
76 &.no-margin {
76 &.no-margin {
77 margin: 0 0 0 0;
77 margin: 0 0 0 0;
78 }
78 }
79
79
80
80
81
81
82 }
82 }
83
83
84
84
85 .btn-default {
85 .btn-default {
86 border: @border-thickness solid @grey5;
86 border: @border-thickness solid @grey5;
87 background-image: none;
87 background-image: none;
88 color: @grey2;
88 color: @grey2;
89
89
90 a {
90 a {
91 color: @grey2;
91 color: @grey2;
92 }
92 }
93
93
94 &:hover,
94 &:hover,
95 &.active {
95 &.active {
96 color: @rcdarkblue;
96 color: @rcdarkblue;
97 background-color: @white;
97 background-color: @white;
98 .border ( @border-thickness, @grey4 );
98 .border ( @border-thickness, @grey4 );
99
99
100 a {
100 a {
101 color: @grey2;
101 color: @grey2;
102 }
102 }
103 }
103 }
104 &:disabled {
104 &:disabled {
105 .border ( @border-thickness-buttons, @grey5 );
105 .border ( @border-thickness-buttons, @grey5 );
106 background-color: transparent;
106 background-color: transparent;
107 }
107 }
108 &.btn-active {
108 &.btn-active {
109 color: @rcdarkblue;
109 color: @rcdarkblue;
110 background-color: @white;
110 background-color: @white;
111 .border ( @border-thickness, @rcdarkblue );
111 .border ( @border-thickness, @rcdarkblue );
112 }
112 }
113 }
113 }
114
114
115 .btn-primary,
115 .btn-primary,
116 .btn-small, /* TODO: anderson: remove .btn-small to not mix with the new btn-sm */
116 .btn-small, /* TODO: anderson: remove .btn-small to not mix with the new btn-sm */
117 .btn-success {
117 .btn-success {
118 .border ( @border-thickness, @rcblue );
118 .border ( @border-thickness, @rcblue );
119 background-color: @rcblue;
119 background-color: @rcblue;
120 color: white;
120 color: white;
121
121
122 a {
122 a {
123 color: white;
123 color: white;
124 }
124 }
125
125
126 &:hover,
126 &:hover,
127 &.active {
127 &.active {
128 .border ( @border-thickness, @rcdarkblue );
128 .border ( @border-thickness, @rcdarkblue );
129 color: white;
129 color: white;
130 background-color: @rcdarkblue;
130 background-color: @rcdarkblue;
131
131
132 a {
132 a {
133 color: white;
133 color: white;
134 }
134 }
135 }
135 }
136 &:disabled {
136 &:disabled {
137 background-color: @rcblue;
137 background-color: @rcblue;
138 }
138 }
139 }
139 }
140
140
141 .btn-secondary {
141 .btn-secondary {
142 &:extend(.btn-default);
142 &:extend(.btn-default);
143
143
144 background-color: white;
144 background-color: white;
145
145
146 &:focus {
146 &:focus {
147 outline: 0;
147 outline: 0;
148 }
148 }
149
149
150 &:hover {
150 &:hover {
151 &:extend(.btn-default:hover);
151 &:extend(.btn-default:hover);
152 }
152 }
153
153
154 &.btn-link {
154 &.btn-link {
155 &:extend(.btn-link);
155 &:extend(.btn-link);
156 color: @rcblue;
156 color: @rcblue;
157 }
157 }
158
158
159 &:disabled {
159 &:disabled {
160 color: @rcblue;
160 color: @rcblue;
161 background-color: white;
161 background-color: white;
162 }
162 }
163 }
163 }
164
164
165 .btn-warning,
165 .btn-warning,
166 .btn-danger,
166 .btn-danger,
167 .revoke_perm,
167 .revoke_perm,
168 .btn-x,
168 .btn-x,
169 .form .action_button.btn-x {
169 .form .action_button.btn-x {
170 .border ( @border-thickness, @alert2 );
170 .border ( @border-thickness, @alert2 );
171 background-color: white;
171 background-color: white;
172 color: @alert2;
172 color: @alert2;
173
173
174 a {
174 a {
175 color: @alert2;
175 color: @alert2;
176 }
176 }
177
177
178 &:hover,
178 &:hover,
179 &.active {
179 &.active {
180 .border ( @border-thickness, @alert2 );
180 .border ( @border-thickness, @alert2 );
181 color: white;
181 color: white;
182 background-color: @alert2;
182 background-color: @alert2;
183
183
184 a {
184 a {
185 color: white;
185 color: white;
186 }
186 }
187 }
187 }
188
188
189 i {
189 i {
190 display:none;
190 display:none;
191 }
191 }
192
192
193 &:disabled {
193 &:disabled {
194 background-color: white;
194 background-color: white;
195 color: @alert2;
195 color: @alert2;
196 }
196 }
197 }
197 }
198
198
199 .btn-approved-status {
199 .btn-approved-status {
200 .border ( @border-thickness, @alert1 );
200 .border ( @border-thickness, @alert1 );
201 background-color: white;
201 background-color: white;
202 color: @alert1;
202 color: @alert1;
203
203
204 }
204 }
205
205
206 .btn-rejected-status {
206 .btn-rejected-status {
207 .border ( @border-thickness, @alert2 );
207 .border ( @border-thickness, @alert2 );
208 background-color: white;
208 background-color: white;
209 color: @alert2;
209 color: @alert2;
210 }
210 }
211
211
212 .btn-sm,
212 .btn-sm,
213 .btn-mini,
213 .btn-mini,
214 .field-sm .btn {
214 .field-sm .btn {
215 padding: @padding/3;
215 padding: @padding/3;
216 }
216 }
217
217
218 .btn-xs {
218 .btn-xs {
219 padding: @padding/4;
219 padding: @padding/4;
220 }
220 }
221
221
222 .btn-lg {
222 .btn-lg {
223 padding: @padding * 1.2;
223 padding: @padding * 1.2;
224 }
224 }
225
225
226 .btn-group {
226 .btn-group {
227 display: inline-block;
227 display: inline-block;
228 .btn {
228 .btn {
229 float: left;
229 float: left;
230 margin: 0 0 0 0;
230 margin: 0 0 0 0;
231 // first item
231 // first item
232 &:first-of-type:not(:last-of-type) {
232 &:first-of-type:not(:last-of-type) {
233 border-radius: @border-radius 0 0 @border-radius;
233 border-radius: @border-radius 0 0 @border-radius;
234
234
235 }
235 }
236 // middle elements
236 // middle elements
237 &:not(:first-of-type):not(:last-of-type) {
237 &:not(:first-of-type):not(:last-of-type) {
238 border-radius: 0;
238 border-radius: 0;
239 border-left-width: 0;
239 border-left-width: 0;
240 border-right-width: 0;
240 border-right-width: 0;
241 }
241 }
242 // last item
242 // last item
243 &:last-of-type:not(:first-of-type) {
243 &:last-of-type:not(:first-of-type) {
244 border-radius: 0 @border-radius @border-radius 0;
244 border-radius: 0 @border-radius @border-radius 0;
245 }
245 }
246
246
247 &:only-child {
247 &:only-child {
248 border-radius: @border-radius;
248 border-radius: @border-radius;
249 }
249 }
250 }
250 }
251
251
252 }
252 }
253
253
254
254
255 .btn-group-actions {
255 .btn-group-actions {
256 position: relative;
256 position: relative;
257 z-index: 100;
257 z-index: 50;
258
258
259 &:not(.open) .btn-action-switcher-container {
259 &:not(.open) .btn-action-switcher-container {
260 display: none;
260 display: none;
261 }
261 }
262
262
263 .btn-more-option {
263 .btn-more-option {
264 margin-left: -1px;
264 margin-left: -1px;
265 padding-left: 2px;
265 padding-left: 2px;
266 padding-right: 2px;
266 padding-right: 2px;
267 border-left: 1px solid @grey3;
267 border-left: 1px solid @grey3;
268 }
268 }
269 }
269 }
270
270
271
271
272 .btn-action-switcher-container {
272 .btn-action-switcher-container {
273 position: absolute;
273 position: absolute;
274 top: 100%;
274 top: 100%;
275
275
276 &.left-align {
276 &.left-align {
277 left: 0;
277 left: 0;
278 }
278 }
279 &.right-align {
279 &.right-align {
280 right: 0;
280 right: 0;
281 }
281 }
282
282
283 }
283 }
284
284
285 .btn-action-switcher {
285 .btn-action-switcher {
286 display: block;
286 display: block;
287 position: relative;
287 position: relative;
288 z-index: 300;
288 z-index: 300;
289 max-width: 600px;
289 max-width: 600px;
290 margin-top: 4px;
290 margin-top: 4px;
291 margin-bottom: 24px;
291 margin-bottom: 24px;
292 font-size: 14px;
292 font-size: 14px;
293 font-weight: 400;
293 font-weight: 400;
294 padding: 8px 0;
294 padding: 8px 0;
295 background-color: #fff;
295 background-color: #fff;
296 border: 1px solid @grey4;
296 border: 1px solid @grey4;
297 border-radius: 3px;
297 border-radius: 3px;
298 box-shadow: @dropdown-shadow;
298 box-shadow: @dropdown-shadow;
299 overflow: auto;
299 overflow: auto;
300
300
301 li {
301 li {
302 display: block;
302 display: block;
303 text-align: left;
303 text-align: left;
304 list-style: none;
304 list-style: none;
305 padding: 5px 10px;
305 padding: 5px 10px;
306 }
306 }
307
307
308 li .action-help-block {
308 li .action-help-block {
309 font-size: 10px;
309 font-size: 10px;
310 line-height: normal;
310 line-height: normal;
311 color: @grey4;
311 color: @grey4;
312 }
312 }
313
313
314 }
314 }
315
315
316 .btn-link {
316 .btn-link {
317 background: transparent;
317 background: transparent;
318 border: none;
318 border: none;
319 padding: 0;
319 padding: 0;
320 color: @rcblue;
320 color: @rcblue;
321
321
322 &:hover {
322 &:hover {
323 background: transparent;
323 background: transparent;
324 border: none;
324 border: none;
325 color: @rcdarkblue;
325 color: @rcdarkblue;
326 }
326 }
327
327
328 //disabled buttons
328 //disabled buttons
329 //last; overrides any other styles
329 //last; overrides any other styles
330 &:disabled {
330 &:disabled {
331 opacity: .7;
331 opacity: .7;
332 cursor: auto;
332 cursor: auto;
333 background-color: white;
333 background-color: white;
334 color: @grey4;
334 color: @grey4;
335 text-shadow: none;
335 text-shadow: none;
336 }
336 }
337
337
338 // TODO: johbo: Check if we can avoid this, indicates that the structure
338 // TODO: johbo: Check if we can avoid this, indicates that the structure
339 // is not yet good.
339 // is not yet good.
340 // lisa: The button CSS reflects the button HTML; both need a cleanup.
340 // lisa: The button CSS reflects the button HTML; both need a cleanup.
341 &.btn-danger {
341 &.btn-danger {
342 color: @alert2;
342 color: @alert2;
343
343
344 &:hover {
344 &:hover {
345 color: darken(@alert2,30%);
345 color: darken(@alert2,30%);
346 }
346 }
347
347
348 &:disabled {
348 &:disabled {
349 color: @alert2;
349 color: @alert2;
350 }
350 }
351 }
351 }
352 }
352 }
353
353
354 .btn-social {
354 .btn-social {
355 &:extend(.btn-default);
355 &:extend(.btn-default);
356 margin: 5px 5px 5px 0px;
356 margin: 5px 5px 5px 0px;
357 min-width: 160px;
357 min-width: 160px;
358 }
358 }
359
359
360 // TODO: johbo: check these exceptions
360 // TODO: johbo: check these exceptions
361
361
362 .links {
362 .links {
363
363
364 .btn + .btn {
364 .btn + .btn {
365 margin-top: @padding;
365 margin-top: @padding;
366 }
366 }
367 }
367 }
368
368
369
369
370 .action_button {
370 .action_button {
371 display:inline;
371 display:inline;
372 margin: 0;
372 margin: 0;
373 padding: 0 1em 0 0;
373 padding: 0 1em 0 0;
374 font-size: inherit;
374 font-size: inherit;
375 color: @rcblue;
375 color: @rcblue;
376 border: none;
376 border: none;
377 border-radius: 0;
377 border-radius: 0;
378 background-color: transparent;
378 background-color: transparent;
379
379
380 &.last-item {
380 &.last-item {
381 border: none;
381 border: none;
382 padding: 0 0 0 0;
382 padding: 0 0 0 0;
383 }
383 }
384
384
385 &:last-child {
385 &:last-child {
386 border: none;
386 border: none;
387 padding: 0 0 0 0;
387 padding: 0 0 0 0;
388 }
388 }
389
389
390 &:hover {
390 &:hover {
391 color: @rcdarkblue;
391 color: @rcdarkblue;
392 background-color: transparent;
392 background-color: transparent;
393 border: none;
393 border: none;
394 }
394 }
395 .noselect
395 .noselect
396 }
396 }
397
397
398 .grid_delete {
398 .grid_delete {
399 .action_button {
399 .action_button {
400 border: none;
400 border: none;
401 }
401 }
402 }
402 }
403
403
404
404
405 // TODO: johbo: Form button tweaks, check if we can use the classes instead
405 // TODO: johbo: Form button tweaks, check if we can use the classes instead
406 input[type="submit"] {
406 input[type="submit"] {
407 &:extend(.btn-primary);
407 &:extend(.btn-primary);
408
408
409 &:focus {
409 &:focus {
410 outline: 0;
410 outline: 0;
411 }
411 }
412
412
413 &:hover {
413 &:hover {
414 &:extend(.btn-primary:hover);
414 &:extend(.btn-primary:hover);
415 }
415 }
416
416
417 &.btn-link {
417 &.btn-link {
418 &:extend(.btn-link);
418 &:extend(.btn-link);
419 color: @rcblue;
419 color: @rcblue;
420
420
421 &:disabled {
421 &:disabled {
422 color: @rcblue;
422 color: @rcblue;
423 background-color: transparent;
423 background-color: transparent;
424 }
424 }
425 }
425 }
426
426
427 &:disabled {
427 &:disabled {
428 .border ( @border-thickness-buttons, @rcblue );
428 .border ( @border-thickness-buttons, @rcblue );
429 background-color: @rcblue;
429 background-color: @rcblue;
430 color: white;
430 color: white;
431 opacity: 0.5;
431 opacity: 0.5;
432 }
432 }
433 }
433 }
434
434
435 input[type="reset"] {
435 input[type="reset"] {
436 &:extend(.btn-default);
436 &:extend(.btn-default);
437
437
438 // TODO: johbo: Check if this tweak can be avoided.
438 // TODO: johbo: Check if this tweak can be avoided.
439 background: transparent;
439 background: transparent;
440
440
441 &:focus {
441 &:focus {
442 outline: 0;
442 outline: 0;
443 }
443 }
444
444
445 &:hover {
445 &:hover {
446 &:extend(.btn-default:hover);
446 &:extend(.btn-default:hover);
447 }
447 }
448
448
449 &.btn-link {
449 &.btn-link {
450 &:extend(.btn-link);
450 &:extend(.btn-link);
451 color: @rcblue;
451 color: @rcblue;
452
452
453 &:disabled {
453 &:disabled {
454 border: none;
454 border: none;
455 }
455 }
456 }
456 }
457
457
458 &:disabled {
458 &:disabled {
459 .border ( @border-thickness-buttons, @rcblue );
459 .border ( @border-thickness-buttons, @rcblue );
460 background-color: white;
460 background-color: white;
461 color: @rcblue;
461 color: @rcblue;
462 }
462 }
463 }
463 }
464
464
465 input[type="submit"],
465 input[type="submit"],
466 input[type="reset"] {
466 input[type="reset"] {
467 &.btn-danger {
467 &.btn-danger {
468 &:extend(.btn-danger);
468 &:extend(.btn-danger);
469
469
470 &:focus {
470 &:focus {
471 outline: 0;
471 outline: 0;
472 }
472 }
473
473
474 &:hover {
474 &:hover {
475 &:extend(.btn-danger:hover);
475 &:extend(.btn-danger:hover);
476 }
476 }
477
477
478 &.btn-link {
478 &.btn-link {
479 &:extend(.btn-link);
479 &:extend(.btn-link);
480 color: @alert2;
480 color: @alert2;
481
481
482 &:hover {
482 &:hover {
483 color: darken(@alert2,30%);
483 color: darken(@alert2,30%);
484 }
484 }
485 }
485 }
486
486
487 &:disabled {
487 &:disabled {
488 color: @alert2;
488 color: @alert2;
489 background-color: white;
489 background-color: white;
490 }
490 }
491 }
491 }
492 &.btn-danger-action {
492 &.btn-danger-action {
493 .border ( @border-thickness, @alert2 );
493 .border ( @border-thickness, @alert2 );
494 background-color: @alert2;
494 background-color: @alert2;
495 color: white;
495 color: white;
496
496
497 a {
497 a {
498 color: white;
498 color: white;
499 }
499 }
500
500
501 &:hover {
501 &:hover {
502 background-color: darken(@alert2,20%);
502 background-color: darken(@alert2,20%);
503 }
503 }
504
504
505 &.active {
505 &.active {
506 .border ( @border-thickness, @alert2 );
506 .border ( @border-thickness, @alert2 );
507 color: white;
507 color: white;
508 background-color: @alert2;
508 background-color: @alert2;
509
509
510 a {
510 a {
511 color: white;
511 color: white;
512 }
512 }
513 }
513 }
514
514
515 &:disabled {
515 &:disabled {
516 background-color: white;
516 background-color: white;
517 color: @alert2;
517 color: @alert2;
518 }
518 }
519 }
519 }
520 }
520 }
521
521
522
522
523 .button-links {
523 .button-links {
524 float: left;
524 float: left;
525 display: inline;
525 display: inline;
526 margin: 0;
526 margin: 0;
527 padding-left: 0;
527 padding-left: 0;
528 list-style: none;
528 list-style: none;
529 text-align: right;
529 text-align: right;
530
530
531 li {
531 li {
532
532
533
533
534 }
534 }
535
535
536 li.active {
536 li.active {
537 background-color: @grey6;
537 background-color: @grey6;
538 .border ( @border-thickness, @grey4 );
538 .border ( @border-thickness, @grey4 );
539 }
539 }
540
540
541 }
541 }
@@ -1,1341 +1,1347 b''
1 // Default styles
1 // Default styles
2
2
3 .diff-collapse {
3 .diff-collapse {
4 margin: @padding 0;
4 margin: @padding 0;
5 text-align: right;
5 text-align: right;
6 }
6 }
7
7
8 .diff-container {
8 .diff-container {
9 margin-bottom: @space;
9 margin-bottom: @space;
10
10
11 .diffblock {
11 .diffblock {
12 margin-bottom: @space;
12 margin-bottom: @space;
13 }
13 }
14
14
15 &.hidden {
15 &.hidden {
16 display: none;
16 display: none;
17 overflow: hidden;
17 overflow: hidden;
18 }
18 }
19 }
19 }
20
20
21
21
22 div.diffblock .sidebyside {
22 div.diffblock .sidebyside {
23 background: #ffffff;
23 background: #ffffff;
24 }
24 }
25
25
26 div.diffblock {
26 div.diffblock {
27 overflow-x: auto;
27 overflow-x: auto;
28 overflow-y: hidden;
28 overflow-y: hidden;
29 clear: both;
29 clear: both;
30 padding: 0px;
30 padding: 0px;
31 background: @grey6;
31 background: @grey6;
32 border: @border-thickness solid @grey5;
32 border: @border-thickness solid @grey5;
33 -webkit-border-radius: @border-radius @border-radius 0px 0px;
33 -webkit-border-radius: @border-radius @border-radius 0px 0px;
34 border-radius: @border-radius @border-radius 0px 0px;
34 border-radius: @border-radius @border-radius 0px 0px;
35
35
36
36
37 .comments-number {
37 .comments-number {
38 float: right;
38 float: right;
39 }
39 }
40
40
41 // BEGIN CODE-HEADER STYLES
41 // BEGIN CODE-HEADER STYLES
42
42
43 .code-header {
43 .code-header {
44 background: @grey6;
44 background: @grey6;
45 padding: 10px 0 10px 0;
45 padding: 10px 0 10px 0;
46 height: auto;
46 height: auto;
47 width: 100%;
47 width: 100%;
48
48
49 .hash {
49 .hash {
50 float: left;
50 float: left;
51 padding: 2px 0 0 2px;
51 padding: 2px 0 0 2px;
52 }
52 }
53
53
54 .date {
54 .date {
55 float: left;
55 float: left;
56 text-transform: uppercase;
56 text-transform: uppercase;
57 padding: 4px 0px 0px 2px;
57 padding: 4px 0px 0px 2px;
58 }
58 }
59
59
60 div {
60 div {
61 margin-left: 4px;
61 margin-left: 4px;
62 }
62 }
63
63
64 div.compare_header {
64 div.compare_header {
65 min-height: 40px;
65 min-height: 40px;
66 margin: 0;
66 margin: 0;
67 padding: 0 @padding;
67 padding: 0 @padding;
68
68
69 .drop-menu {
69 .drop-menu {
70 float:left;
70 float:left;
71 display: block;
71 display: block;
72 margin:0 0 @padding 0;
72 margin:0 0 @padding 0;
73 }
73 }
74
74
75 .compare-label {
75 .compare-label {
76 float: left;
76 float: left;
77 clear: both;
77 clear: both;
78 display: inline-block;
78 display: inline-block;
79 min-width: 5em;
79 min-width: 5em;
80 margin: 0;
80 margin: 0;
81 padding: @button-padding @button-padding @button-padding 0;
81 padding: @button-padding @button-padding @button-padding 0;
82 font-weight: @text-semibold-weight;
82 font-weight: @text-semibold-weight;
83 font-family: @text-semibold;
83 font-family: @text-semibold;
84 }
84 }
85
85
86 .compare-buttons {
86 .compare-buttons {
87 float: left;
87 float: left;
88 margin: 0;
88 margin: 0;
89 padding: 0 0 @padding;
89 padding: 0 0 @padding;
90
90
91 .btn {
91 .btn {
92 margin: 0 @padding 0 0;
92 margin: 0 @padding 0 0;
93 }
93 }
94 }
94 }
95 }
95 }
96
96
97 }
97 }
98
98
99 .parents {
99 .parents {
100 float: left;
100 float: left;
101 width: 100px;
101 width: 100px;
102 font-weight: 400;
102 font-weight: 400;
103 vertical-align: middle;
103 vertical-align: middle;
104 padding: 0px 2px 0px 2px;
104 padding: 0px 2px 0px 2px;
105 background-color: @grey6;
105 background-color: @grey6;
106
106
107 #parent_link {
107 #parent_link {
108 margin: 00px 2px;
108 margin: 00px 2px;
109
109
110 &.double {
110 &.double {
111 margin: 0px 2px;
111 margin: 0px 2px;
112 }
112 }
113
113
114 &.disabled{
114 &.disabled{
115 margin-right: @padding;
115 margin-right: @padding;
116 }
116 }
117 }
117 }
118 }
118 }
119
119
120 .children {
120 .children {
121 float: right;
121 float: right;
122 width: 100px;
122 width: 100px;
123 font-weight: 400;
123 font-weight: 400;
124 vertical-align: middle;
124 vertical-align: middle;
125 text-align: right;
125 text-align: right;
126 padding: 0px 2px 0px 2px;
126 padding: 0px 2px 0px 2px;
127 background-color: @grey6;
127 background-color: @grey6;
128
128
129 #child_link {
129 #child_link {
130 margin: 0px 2px;
130 margin: 0px 2px;
131
131
132 &.double {
132 &.double {
133 margin: 0px 2px;
133 margin: 0px 2px;
134 }
134 }
135
135
136 &.disabled{
136 &.disabled{
137 margin-right: @padding;
137 margin-right: @padding;
138 }
138 }
139 }
139 }
140 }
140 }
141
141
142 .changeset_header {
142 .changeset_header {
143 height: 16px;
143 height: 16px;
144
144
145 & > div{
145 & > div{
146 margin-right: @padding;
146 margin-right: @padding;
147 }
147 }
148 }
148 }
149
149
150 .changeset_file {
150 .changeset_file {
151 text-align: left;
151 text-align: left;
152 float: left;
152 float: left;
153 padding: 0;
153 padding: 0;
154
154
155 a{
155 a{
156 display: inline-block;
156 display: inline-block;
157 margin-right: 0.5em;
157 margin-right: 0.5em;
158 }
158 }
159
159
160 #selected_mode{
160 #selected_mode{
161 margin-left: 0;
161 margin-left: 0;
162 }
162 }
163 }
163 }
164
164
165 .diff-menu-wrapper {
165 .diff-menu-wrapper {
166 float: left;
166 float: left;
167 }
167 }
168
168
169 .diff-menu {
169 .diff-menu {
170 position: absolute;
170 position: absolute;
171 background: none repeat scroll 0 0 #FFFFFF;
171 background: none repeat scroll 0 0 #FFFFFF;
172 border-color: #003367 @grey3 @grey3;
172 border-color: #003367 @grey3 @grey3;
173 border-right: 1px solid @grey3;
173 border-right: 1px solid @grey3;
174 border-style: solid solid solid;
174 border-style: solid solid solid;
175 border-width: @border-thickness;
175 border-width: @border-thickness;
176 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
176 box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
177 margin-top: 5px;
177 margin-top: 5px;
178 margin-left: 1px;
178 margin-left: 1px;
179 }
179 }
180
180
181 .diff-actions, .editor-actions {
181 .diff-actions, .editor-actions {
182 float: left;
182 float: left;
183
183
184 input{
184 input{
185 margin: 0 0.5em 0 0;
185 margin: 0 0.5em 0 0;
186 }
186 }
187 }
187 }
188
188
189 // END CODE-HEADER STYLES
189 // END CODE-HEADER STYLES
190
190
191 // BEGIN CODE-BODY STYLES
191 // BEGIN CODE-BODY STYLES
192
192
193 .code-body {
193 .code-body {
194 padding: 0;
194 padding: 0;
195 background-color: #ffffff;
195 background-color: #ffffff;
196 position: relative;
196 position: relative;
197 max-width: none;
197 max-width: none;
198 box-sizing: border-box;
198 box-sizing: border-box;
199 // TODO: johbo: Parent has overflow: auto, this forces the child here
199 // TODO: johbo: Parent has overflow: auto, this forces the child here
200 // to have the intended size and to scroll. Should be simplified.
200 // to have the intended size and to scroll. Should be simplified.
201 width: 100%;
201 width: 100%;
202 overflow-x: auto;
202 overflow-x: auto;
203 }
203 }
204
204
205 pre.raw {
205 pre.raw {
206 background: white;
206 background: white;
207 color: @grey1;
207 color: @grey1;
208 }
208 }
209 // END CODE-BODY STYLES
209 // END CODE-BODY STYLES
210
210
211 }
211 }
212
212
213
213
214 table.code-difftable {
214 table.code-difftable {
215 border-collapse: collapse;
215 border-collapse: collapse;
216 width: 99%;
216 width: 99%;
217 border-radius: 0px !important;
217 border-radius: 0px !important;
218
218
219 td {
219 td {
220 padding: 0 !important;
220 padding: 0 !important;
221 background: none !important;
221 background: none !important;
222 border: 0 !important;
222 border: 0 !important;
223 }
223 }
224
224
225 .context {
225 .context {
226 background: none repeat scroll 0 0 #DDE7EF;
226 background: none repeat scroll 0 0 #DDE7EF;
227 }
227 }
228
228
229 .add {
229 .add {
230 background: none repeat scroll 0 0 #DDFFDD;
230 background: none repeat scroll 0 0 #DDFFDD;
231
231
232 ins {
232 ins {
233 background: none repeat scroll 0 0 #AAFFAA;
233 background: none repeat scroll 0 0 #AAFFAA;
234 text-decoration: none;
234 text-decoration: none;
235 }
235 }
236 }
236 }
237
237
238 .del {
238 .del {
239 background: none repeat scroll 0 0 #FFDDDD;
239 background: none repeat scroll 0 0 #FFDDDD;
240
240
241 del {
241 del {
242 background: none repeat scroll 0 0 #FFAAAA;
242 background: none repeat scroll 0 0 #FFAAAA;
243 text-decoration: none;
243 text-decoration: none;
244 }
244 }
245 }
245 }
246
246
247 /** LINE NUMBERS **/
247 /** LINE NUMBERS **/
248 .lineno {
248 .lineno {
249 padding-left: 2px !important;
249 padding-left: 2px !important;
250 padding-right: 2px;
250 padding-right: 2px;
251 text-align: right;
251 text-align: right;
252 width: 32px;
252 width: 32px;
253 -moz-user-select: none;
253 -moz-user-select: none;
254 -webkit-user-select: none;
254 -webkit-user-select: none;
255 border-right: @border-thickness solid @grey5 !important;
255 border-right: @border-thickness solid @grey5 !important;
256 border-left: 0px solid #CCC !important;
256 border-left: 0px solid #CCC !important;
257 border-top: 0px solid #CCC !important;
257 border-top: 0px solid #CCC !important;
258 border-bottom: none !important;
258 border-bottom: none !important;
259
259
260 a {
260 a {
261 &:extend(pre);
261 &:extend(pre);
262 text-align: right;
262 text-align: right;
263 padding-right: 2px;
263 padding-right: 2px;
264 cursor: pointer;
264 cursor: pointer;
265 display: block;
265 display: block;
266 width: 32px;
266 width: 32px;
267 }
267 }
268 }
268 }
269
269
270 .context {
270 .context {
271 cursor: auto;
271 cursor: auto;
272 &:extend(pre);
272 &:extend(pre);
273 }
273 }
274
274
275 .lineno-inline {
275 .lineno-inline {
276 background: none repeat scroll 0 0 #FFF !important;
276 background: none repeat scroll 0 0 #FFF !important;
277 padding-left: 2px;
277 padding-left: 2px;
278 padding-right: 2px;
278 padding-right: 2px;
279 text-align: right;
279 text-align: right;
280 width: 30px;
280 width: 30px;
281 -moz-user-select: none;
281 -moz-user-select: none;
282 -webkit-user-select: none;
282 -webkit-user-select: none;
283 }
283 }
284
284
285 /** CODE **/
285 /** CODE **/
286 .code {
286 .code {
287 display: block;
287 display: block;
288 width: 100%;
288 width: 100%;
289
289
290 td {
290 td {
291 margin: 0;
291 margin: 0;
292 padding: 0;
292 padding: 0;
293 }
293 }
294
294
295 pre {
295 pre {
296 margin: 0;
296 margin: 0;
297 padding: 0;
297 padding: 0;
298 margin-left: .5em;
298 margin-left: .5em;
299 }
299 }
300 }
300 }
301 }
301 }
302
302
303
303
304 // Comments
304 // Comments
305 .comment-selected-hl {
305 .comment-selected-hl {
306 border-left: 6px solid @comment-highlight-color !important;
306 border-left: 6px solid @comment-highlight-color !important;
307 padding-left: 3px !important;
307 padding-left: 3px !important;
308 margin-left: -7px !important;
308 margin-left: -7px !important;
309 }
309 }
310
310
311 div.comment:target,
311 div.comment:target,
312 div.comment-outdated:target {
312 div.comment-outdated:target {
313 .comment-selected-hl;
313 .comment-selected-hl;
314 }
314 }
315
315
316 //TODO: anderson: can't get an absolute number out of anything, so had to put the
316 //TODO: anderson: can't get an absolute number out of anything, so had to put the
317 //current values that might change. But to make it clear I put as a calculation
317 //current values that might change. But to make it clear I put as a calculation
318 @comment-max-width: 1065px;
318 @comment-max-width: 1065px;
319 @pr-extra-margin: 34px;
319 @pr-extra-margin: 34px;
320 @pr-border-spacing: 4px;
320 @pr-border-spacing: 4px;
321 @pr-comment-width: @comment-max-width - @pr-extra-margin - @pr-border-spacing;
321 @pr-comment-width: @comment-max-width - @pr-extra-margin - @pr-border-spacing;
322
322
323 // Pull Request
323 // Pull Request
324 .cs_files .code-difftable {
324 .cs_files .code-difftable {
325 border: @border-thickness solid @grey5; //borders only on PRs
325 border: @border-thickness solid @grey5; //borders only on PRs
326
326
327 .comment-inline-form,
327 .comment-inline-form,
328 div.comment {
328 div.comment {
329 width: @pr-comment-width;
329 width: @pr-comment-width;
330 }
330 }
331 }
331 }
332
332
333 // Changeset
333 // Changeset
334 .code-difftable {
334 .code-difftable {
335 .comment-inline-form,
335 .comment-inline-form,
336 div.comment {
336 div.comment {
337 width: @comment-max-width;
337 width: @comment-max-width;
338 }
338 }
339 }
339 }
340
340
341 //Style page
341 //Style page
342 @style-extra-margin: @sidebar-width + (@sidebarpadding * 3) + @padding;
342 @style-extra-margin: @sidebar-width + (@sidebarpadding * 3) + @padding;
343 #style-page .code-difftable{
343 #style-page .code-difftable{
344 .comment-inline-form,
344 .comment-inline-form,
345 div.comment {
345 div.comment {
346 width: @comment-max-width - @style-extra-margin;
346 width: @comment-max-width - @style-extra-margin;
347 }
347 }
348 }
348 }
349
349
350 #context-bar > h2 {
350 #context-bar > h2 {
351 font-size: 20px;
351 font-size: 20px;
352 }
352 }
353
353
354 #context-bar > h2> a {
354 #context-bar > h2> a {
355 font-size: 20px;
355 font-size: 20px;
356 }
356 }
357 // end of defaults
357 // end of defaults
358
358
359 .file_diff_buttons {
359 .file_diff_buttons {
360 padding: 0 0 @padding;
360 padding: 0 0 @padding;
361
361
362 .drop-menu {
362 .drop-menu {
363 float: left;
363 float: left;
364 margin: 0 @padding 0 0;
364 margin: 0 @padding 0 0;
365 }
365 }
366 .btn {
366 .btn {
367 margin: 0 @padding 0 0;
367 margin: 0 @padding 0 0;
368 }
368 }
369 }
369 }
370
370
371 .code-body.textarea.editor {
371 .code-body.textarea.editor {
372 max-width: none;
372 max-width: none;
373 padding: 15px;
373 padding: 15px;
374 }
374 }
375
375
376 td.injected_diff{
376 td.injected_diff{
377 max-width: 1178px;
377 max-width: 1178px;
378 overflow-x: auto;
378 overflow-x: auto;
379 overflow-y: hidden;
379 overflow-y: hidden;
380
380
381 div.diff-container,
381 div.diff-container,
382 div.diffblock{
382 div.diffblock{
383 max-width: 100%;
383 max-width: 100%;
384 }
384 }
385
385
386 div.code-body {
386 div.code-body {
387 max-width: 1124px;
387 max-width: 1124px;
388 overflow-x: auto;
388 overflow-x: auto;
389 overflow-y: hidden;
389 overflow-y: hidden;
390 padding: 0;
390 padding: 0;
391 }
391 }
392 div.diffblock {
392 div.diffblock {
393 border: none;
393 border: none;
394 }
394 }
395
395
396 &.inline-form {
396 &.inline-form {
397 width: 99%
397 width: 99%
398 }
398 }
399 }
399 }
400
400
401
401
402 table.code-difftable {
402 table.code-difftable {
403 width: 100%;
403 width: 100%;
404 }
404 }
405
405
406 /** PYGMENTS COLORING **/
406 /** PYGMENTS COLORING **/
407 div.codeblock {
407 div.codeblock {
408
408
409 // TODO: johbo: Added interim to get rid of the margin around
409 // TODO: johbo: Added interim to get rid of the margin around
410 // Select2 widgets. This needs further cleanup.
410 // Select2 widgets. This needs further cleanup.
411 overflow: auto;
411 overflow: auto;
412 padding: 0px;
412 padding: 0px;
413 border: @border-thickness solid @grey6;
413 border: @border-thickness solid @grey6;
414 .border-radius(@border-radius);
414 .border-radius(@border-radius);
415
415
416 #remove_gist {
416 #remove_gist {
417 float: right;
417 float: right;
418 }
418 }
419
419
420 .gist_url {
420 .gist_url {
421 padding: 0px 0px 35px 0px;
421 padding: 0px 0px 35px 0px;
422 }
422 }
423
423
424 .gist-desc {
424 .gist-desc {
425 clear: both;
425 clear: both;
426 margin: 0 0 10px 0;
426 margin: 0 0 10px 0;
427 code {
427 code {
428 white-space: pre-line;
428 white-space: pre-line;
429 line-height: inherit
429 line-height: inherit
430 }
430 }
431 }
431 }
432
432
433 .author {
433 .author {
434 clear: both;
434 clear: both;
435 vertical-align: middle;
435 vertical-align: middle;
436 font-weight: @text-bold-weight;
436 font-weight: @text-bold-weight;
437 font-family: @text-bold;
437 font-family: @text-bold;
438 }
438 }
439
439
440 .btn-mini {
440 .btn-mini {
441 float: left;
441 float: left;
442 margin: 0 5px 0 0;
442 margin: 0 5px 0 0;
443 }
443 }
444
444
445 .code-header {
445 .code-header {
446 padding: @padding;
446 padding: @padding;
447 border-bottom: @border-thickness solid @grey5;
447 border-bottom: @border-thickness solid @grey5;
448
448
449 .rc-user {
449 .rc-user {
450 min-width: 0;
450 min-width: 0;
451 margin-right: .5em;
451 margin-right: .5em;
452 }
452 }
453
453
454 .stats {
454 .stats {
455 clear: both;
455 clear: both;
456 margin: 0 0 @padding 0;
456 margin: 0 0 @padding 0;
457 padding: 0;
457 padding: 0;
458 .left {
458 .left {
459 float: left;
459 float: left;
460 clear: left;
460 clear: left;
461 max-width: 75%;
461 max-width: 75%;
462 margin: 0 0 @padding 0;
462 margin: 0 0 @padding 0;
463
463
464 &.item {
464 &.item {
465 margin-right: @padding;
465 margin-right: @padding;
466 &.last { border-right: none; }
466 &.last { border-right: none; }
467 }
467 }
468 }
468 }
469 .buttons { float: right; }
469 .buttons { float: right; }
470 .author {
470 .author {
471 height: 25px; margin-left: 15px; font-weight: bold;
471 height: 25px; margin-left: 15px; font-weight: bold;
472 }
472 }
473 }
473 }
474
474
475 .commit {
475 .commit {
476 margin: 5px 0 0 26px;
476 margin: 5px 0 0 26px;
477 font-weight: normal;
477 font-weight: normal;
478 white-space: pre-wrap;
478 white-space: pre-wrap;
479 }
479 }
480 }
480 }
481
481
482 .message {
482 .message {
483 position: relative;
483 position: relative;
484 margin: @padding;
484 margin: @padding;
485
485
486 .codeblock-label {
486 .codeblock-label {
487 margin: 0 0 1em 0;
487 margin: 0 0 1em 0;
488 }
488 }
489 }
489 }
490
490
491 .code-body {
491 .code-body {
492 padding: 0.8em 1em;
492 padding: 0.8em 1em;
493 background-color: #ffffff;
493 background-color: #ffffff;
494 min-width: 100%;
494 min-width: 100%;
495 box-sizing: border-box;
495 box-sizing: border-box;
496 // TODO: johbo: Parent has overflow: auto, this forces the child here
496 // TODO: johbo: Parent has overflow: auto, this forces the child here
497 // to have the intended size and to scroll. Should be simplified.
497 // to have the intended size and to scroll. Should be simplified.
498 width: 100%;
498 width: 100%;
499 overflow-x: auto;
499 overflow-x: auto;
500
500
501 img.rendered-binary {
501 img.rendered-binary {
502 height: auto;
502 height: auto;
503 width: auto;
503 width: auto;
504 }
504 }
505
505
506 .markdown-block {
506 .markdown-block {
507 padding: 1em 0;
507 padding: 1em 0;
508 }
508 }
509 }
509 }
510
510
511 .codeblock-header {
511 .codeblock-header {
512 background: @grey7;
512 background: @grey7;
513 height: 36px;
513 height: 36px;
514 }
514 }
515
515
516 .path {
516 .path {
517 border-bottom: 1px solid @grey6;
517 border-bottom: 1px solid @grey6;
518 padding: .65em 1em;
518 padding: .65em 1em;
519 height: 18px;
519 height: 18px;
520 }
520 }
521 }
521 }
522
522
523 .code-highlighttable,
523 .code-highlighttable,
524 div.codeblock {
524 div.codeblock {
525
525
526 &.readme {
526 &.readme {
527 background-color: white;
527 background-color: white;
528 }
528 }
529
529
530 .markdown-block table {
530 .markdown-block table {
531 border-collapse: collapse;
531 border-collapse: collapse;
532
532
533 th,
533 th,
534 td {
534 td {
535 padding: .5em;
535 padding: .5em;
536 border: @border-thickness solid @border-default-color;
536 border: @border-thickness solid @border-default-color;
537 }
537 }
538 }
538 }
539
539
540 table {
540 table {
541 border: 0px;
541 border: 0px;
542 margin: 0;
542 margin: 0;
543 letter-spacing: normal;
543 letter-spacing: normal;
544
544
545
545
546 td {
546 td {
547 border: 0px;
547 border: 0px;
548 vertical-align: top;
548 vertical-align: top;
549 }
549 }
550 }
550 }
551 }
551 }
552
552
553 div.codeblock .code-header .search-path { padding: 0 0 0 10px; }
553 div.codeblock .code-header .search-path { padding: 0 0 0 10px; }
554 div.search-code-body {
554 div.search-code-body {
555 background-color: #ffffff; padding: 5px 0 5px 10px;
555 background-color: #ffffff; padding: 5px 0 5px 10px;
556 pre {
556 pre {
557 .match { background-color: #faffa6;}
557 .match { background-color: #faffa6;}
558 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
558 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
559 }
559 }
560 .code-highlighttable {
560 .code-highlighttable {
561 border-collapse: collapse;
561 border-collapse: collapse;
562
562
563 tr:hover {
563 tr:hover {
564 background: #fafafa;
564 background: #fafafa;
565 }
565 }
566 td.code {
566 td.code {
567 padding-left: 10px;
567 padding-left: 10px;
568 }
568 }
569 td.line {
569 td.line {
570 border-right: 1px solid #ccc !important;
570 border-right: 1px solid #ccc !important;
571 padding-right: 10px;
571 padding-right: 10px;
572 text-align: right;
572 text-align: right;
573 font-family: @text-monospace;
573 font-family: @text-monospace;
574 span {
574 span {
575 white-space: pre-wrap;
575 white-space: pre-wrap;
576 color: #666666;
576 color: #666666;
577 }
577 }
578 }
578 }
579 }
579 }
580 }
580 }
581
581
582 div.annotatediv { margin-left: 2px; margin-right: 4px; }
582 div.annotatediv { margin-left: 2px; margin-right: 4px; }
583 .code-highlight {
583 .code-highlight {
584 margin: 0; padding: 0; border-left: @border-thickness solid @grey5;
584 margin: 0; padding: 0; border-left: @border-thickness solid @grey5;
585 pre, .linenodiv pre { padding: 0 5px; margin: 0; }
585 pre, .linenodiv pre { padding: 0 5px; margin: 0; }
586 pre div:target {background-color: @comment-highlight-color !important;}
586 pre div:target {background-color: @comment-highlight-color !important;}
587 }
587 }
588
588
589 .linenos a { text-decoration: none; }
589 .linenos a { text-decoration: none; }
590
590
591 .CodeMirror-selected { background: @rchighlightblue; }
591 .CodeMirror-selected { background: @rchighlightblue; }
592 .CodeMirror-focused .CodeMirror-selected { background: @rchighlightblue; }
592 .CodeMirror-focused .CodeMirror-selected { background: @rchighlightblue; }
593 .CodeMirror ::selection { background: @rchighlightblue; }
593 .CodeMirror ::selection { background: @rchighlightblue; }
594 .CodeMirror ::-moz-selection { background: @rchighlightblue; }
594 .CodeMirror ::-moz-selection { background: @rchighlightblue; }
595
595
596 .code { display: block; border:0px !important; }
596 .code { display: block; border:0px !important; }
597
597
598 .code-highlight, /* TODO: dan: merge codehilite into code-highlight */
598 .code-highlight, /* TODO: dan: merge codehilite into code-highlight */
599 .codehilite {
599 .codehilite {
600 /*ElasticMatch is custom RhodeCode TAG*/
600 /*ElasticMatch is custom RhodeCode TAG*/
601
601
602 .c-ElasticMatch {
602 .c-ElasticMatch {
603 background-color: #faffa6;
603 background-color: #faffa6;
604 padding: 0.2em;
604 padding: 0.2em;
605 }
605 }
606 }
606 }
607
607
608 /* This can be generated with `pygmentize -S default -f html` */
608 /* This can be generated with `pygmentize -S default -f html` */
609 .code-highlight,
609 .code-highlight,
610 .codehilite {
610 .codehilite {
611 /*ElasticMatch is custom RhodeCode TAG*/
611 /*ElasticMatch is custom RhodeCode TAG*/
612 .c-ElasticMatch { background-color: #faffa6; padding: 0.2em;}
612 .c-ElasticMatch { background-color: #faffa6; padding: 0.2em;}
613 .hll { background-color: #ffffcc }
613 .hll { background-color: #ffffcc }
614 .c { color: #408080; font-style: italic } /* Comment */
614 .c { color: #408080; font-style: italic } /* Comment */
615 .err, .codehilite .err { border: none } /* Error */
615 .err, .codehilite .err { border: none } /* Error */
616 .k { color: #008000; font-weight: bold } /* Keyword */
616 .k { color: #008000; font-weight: bold } /* Keyword */
617 .o { color: #666666 } /* Operator */
617 .o { color: #666666 } /* Operator */
618 .ch { color: #408080; font-style: italic } /* Comment.Hashbang */
618 .ch { color: #408080; font-style: italic } /* Comment.Hashbang */
619 .cm { color: #408080; font-style: italic } /* Comment.Multiline */
619 .cm { color: #408080; font-style: italic } /* Comment.Multiline */
620 .cp { color: #BC7A00 } /* Comment.Preproc */
620 .cp { color: #BC7A00 } /* Comment.Preproc */
621 .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */
621 .cpf { color: #408080; font-style: italic } /* Comment.PreprocFile */
622 .c1 { color: #408080; font-style: italic } /* Comment.Single */
622 .c1 { color: #408080; font-style: italic } /* Comment.Single */
623 .cs { color: #408080; font-style: italic } /* Comment.Special */
623 .cs { color: #408080; font-style: italic } /* Comment.Special */
624 .gd { color: #A00000 } /* Generic.Deleted */
624 .gd { color: #A00000 } /* Generic.Deleted */
625 .ge { font-style: italic } /* Generic.Emph */
625 .ge { font-style: italic } /* Generic.Emph */
626 .gr { color: #FF0000 } /* Generic.Error */
626 .gr { color: #FF0000 } /* Generic.Error */
627 .gh { color: #000080; font-weight: bold } /* Generic.Heading */
627 .gh { color: #000080; font-weight: bold } /* Generic.Heading */
628 .gi { color: #00A000 } /* Generic.Inserted */
628 .gi { color: #00A000 } /* Generic.Inserted */
629 .go { color: #888888 } /* Generic.Output */
629 .go { color: #888888 } /* Generic.Output */
630 .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
630 .gp { color: #000080; font-weight: bold } /* Generic.Prompt */
631 .gs { font-weight: bold } /* Generic.Strong */
631 .gs { font-weight: bold } /* Generic.Strong */
632 .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
632 .gu { color: #800080; font-weight: bold } /* Generic.Subheading */
633 .gt { color: #0044DD } /* Generic.Traceback */
633 .gt { color: #0044DD } /* Generic.Traceback */
634 .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
634 .kc { color: #008000; font-weight: bold } /* Keyword.Constant */
635 .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
635 .kd { color: #008000; font-weight: bold } /* Keyword.Declaration */
636 .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
636 .kn { color: #008000; font-weight: bold } /* Keyword.Namespace */
637 .kp { color: #008000 } /* Keyword.Pseudo */
637 .kp { color: #008000 } /* Keyword.Pseudo */
638 .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
638 .kr { color: #008000; font-weight: bold } /* Keyword.Reserved */
639 .kt { color: #B00040 } /* Keyword.Type */
639 .kt { color: #B00040 } /* Keyword.Type */
640 .m { color: #666666 } /* Literal.Number */
640 .m { color: #666666 } /* Literal.Number */
641 .s { color: #BA2121 } /* Literal.String */
641 .s { color: #BA2121 } /* Literal.String */
642 .na { color: #7D9029 } /* Name.Attribute */
642 .na { color: #7D9029 } /* Name.Attribute */
643 .nb { color: #008000 } /* Name.Builtin */
643 .nb { color: #008000 } /* Name.Builtin */
644 .nc { color: #0000FF; font-weight: bold } /* Name.Class */
644 .nc { color: #0000FF; font-weight: bold } /* Name.Class */
645 .no { color: #880000 } /* Name.Constant */
645 .no { color: #880000 } /* Name.Constant */
646 .nd { color: #AA22FF } /* Name.Decorator */
646 .nd { color: #AA22FF } /* Name.Decorator */
647 .ni { color: #999999; font-weight: bold } /* Name.Entity */
647 .ni { color: #999999; font-weight: bold } /* Name.Entity */
648 .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
648 .ne { color: #D2413A; font-weight: bold } /* Name.Exception */
649 .nf { color: #0000FF } /* Name.Function */
649 .nf { color: #0000FF } /* Name.Function */
650 .nl { color: #A0A000 } /* Name.Label */
650 .nl { color: #A0A000 } /* Name.Label */
651 .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
651 .nn { color: #0000FF; font-weight: bold } /* Name.Namespace */
652 .nt { color: #008000; font-weight: bold } /* Name.Tag */
652 .nt { color: #008000; font-weight: bold } /* Name.Tag */
653 .nv { color: #19177C } /* Name.Variable */
653 .nv { color: #19177C } /* Name.Variable */
654 .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
654 .ow { color: #AA22FF; font-weight: bold } /* Operator.Word */
655 .w { color: #bbbbbb } /* Text.Whitespace */
655 .w { color: #bbbbbb } /* Text.Whitespace */
656 .mb { color: #666666 } /* Literal.Number.Bin */
656 .mb { color: #666666 } /* Literal.Number.Bin */
657 .mf { color: #666666 } /* Literal.Number.Float */
657 .mf { color: #666666 } /* Literal.Number.Float */
658 .mh { color: #666666 } /* Literal.Number.Hex */
658 .mh { color: #666666 } /* Literal.Number.Hex */
659 .mi { color: #666666 } /* Literal.Number.Integer */
659 .mi { color: #666666 } /* Literal.Number.Integer */
660 .mo { color: #666666 } /* Literal.Number.Oct */
660 .mo { color: #666666 } /* Literal.Number.Oct */
661 .sa { color: #BA2121 } /* Literal.String.Affix */
661 .sa { color: #BA2121 } /* Literal.String.Affix */
662 .sb { color: #BA2121 } /* Literal.String.Backtick */
662 .sb { color: #BA2121 } /* Literal.String.Backtick */
663 .sc { color: #BA2121 } /* Literal.String.Char */
663 .sc { color: #BA2121 } /* Literal.String.Char */
664 .dl { color: #BA2121 } /* Literal.String.Delimiter */
664 .dl { color: #BA2121 } /* Literal.String.Delimiter */
665 .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
665 .sd { color: #BA2121; font-style: italic } /* Literal.String.Doc */
666 .s2 { color: #BA2121 } /* Literal.String.Double */
666 .s2 { color: #BA2121 } /* Literal.String.Double */
667 .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
667 .se { color: #BB6622; font-weight: bold } /* Literal.String.Escape */
668 .sh { color: #BA2121 } /* Literal.String.Heredoc */
668 .sh { color: #BA2121 } /* Literal.String.Heredoc */
669 .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
669 .si { color: #BB6688; font-weight: bold } /* Literal.String.Interpol */
670 .sx { color: #008000 } /* Literal.String.Other */
670 .sx { color: #008000 } /* Literal.String.Other */
671 .sr { color: #BB6688 } /* Literal.String.Regex */
671 .sr { color: #BB6688 } /* Literal.String.Regex */
672 .s1 { color: #BA2121 } /* Literal.String.Single */
672 .s1 { color: #BA2121 } /* Literal.String.Single */
673 .ss { color: #19177C } /* Literal.String.Symbol */
673 .ss { color: #19177C } /* Literal.String.Symbol */
674 .bp { color: #008000 } /* Name.Builtin.Pseudo */
674 .bp { color: #008000 } /* Name.Builtin.Pseudo */
675 .fm { color: #0000FF } /* Name.Function.Magic */
675 .fm { color: #0000FF } /* Name.Function.Magic */
676 .vc { color: #19177C } /* Name.Variable.Class */
676 .vc { color: #19177C } /* Name.Variable.Class */
677 .vg { color: #19177C } /* Name.Variable.Global */
677 .vg { color: #19177C } /* Name.Variable.Global */
678 .vi { color: #19177C } /* Name.Variable.Instance */
678 .vi { color: #19177C } /* Name.Variable.Instance */
679 .vm { color: #19177C } /* Name.Variable.Magic */
679 .vm { color: #19177C } /* Name.Variable.Magic */
680 .il { color: #666666 } /* Literal.Number.Integer.Long */
680 .il { color: #666666 } /* Literal.Number.Integer.Long */
681
681
682 }
682 }
683
683
684 /* customized pre blocks for markdown/rst */
684 /* customized pre blocks for markdown/rst */
685 pre.literal-block, .codehilite pre{
685 pre.literal-block, .codehilite pre{
686 padding: @padding;
686 padding: @padding;
687 border: 1px solid @grey6;
687 border: 1px solid @grey6;
688 .border-radius(@border-radius);
688 .border-radius(@border-radius);
689 background-color: @grey7;
689 background-color: @grey7;
690 }
690 }
691
691
692
692
693 /* START NEW CODE BLOCK CSS */
693 /* START NEW CODE BLOCK CSS */
694
694
695 @cb-line-height: 18px;
695 @cb-line-height: 18px;
696 @cb-line-code-padding: 10px;
696 @cb-line-code-padding: 10px;
697 @cb-text-padding: 5px;
697 @cb-text-padding: 5px;
698
698
699 @pill-padding: 2px 7px;
699 @pill-padding: 2px 7px;
700 @pill-padding-small: 2px 2px 1px 2px;
700 @pill-padding-small: 2px 2px 1px 2px;
701
701
702 input.filediff-collapse-state {
702 input.filediff-collapse-state {
703 display: none;
703 display: none;
704
704
705 &:checked + .filediff { /* file diff is collapsed */
705 &:checked + .filediff { /* file diff is collapsed */
706 .cb {
706 .cb {
707 display: none
707 display: none
708 }
708 }
709 .filediff-collapse-indicator {
709 .filediff-collapse-indicator {
710 float: left;
710 float: left;
711 cursor: pointer;
711 cursor: pointer;
712 margin: 1px -5px;
712 margin: 1px -5px;
713 }
713 }
714 .filediff-collapse-indicator:before {
714 .filediff-collapse-indicator:before {
715 content: '\f105';
715 content: '\f105';
716 }
716 }
717
717
718 .filediff-menu {
718 .filediff-menu {
719 display: none;
719 display: none;
720 }
720 }
721
721
722 }
722 }
723
723
724 &+ .filediff { /* file diff is expanded */
724 &+ .filediff { /* file diff is expanded */
725
725
726 .filediff-collapse-indicator {
726 .filediff-collapse-indicator {
727 float: left;
727 float: left;
728 cursor: pointer;
728 cursor: pointer;
729 margin: 1px -5px;
729 margin: 1px -5px;
730 }
730 }
731 .filediff-collapse-indicator:before {
731 .filediff-collapse-indicator:before {
732 content: '\f107';
732 content: '\f107';
733 }
733 }
734
734
735 .filediff-menu {
735 .filediff-menu {
736 display: block;
736 display: block;
737 }
737 }
738
738
739 margin: 10px 0;
739 margin: 10px 0;
740 &:nth-child(2) {
740 &:nth-child(2) {
741 margin: 0;
741 margin: 0;
742 }
742 }
743 }
743 }
744 }
744 }
745
745
746 .filediffs .anchor {
746 .filediffs .anchor {
747 display: block;
747 display: block;
748 height: 40px;
748 height: 40px;
749 margin-top: -40px;
749 margin-top: -40px;
750 visibility: hidden;
750 visibility: hidden;
751 }
751 }
752
752
753 .filediffs .anchor:nth-of-type(1) {
753 .filediffs .anchor:nth-of-type(1) {
754 display: block;
754 display: block;
755 height: 80px;
755 height: 80px;
756 margin-top: -80px;
756 margin-top: -80px;
757 visibility: hidden;
757 visibility: hidden;
758 }
758 }
759
759
760 .cs_files {
760 .cs_files {
761 clear: both;
761 clear: both;
762 }
762 }
763
763
764 #diff-file-sticky{
764 #diff-file-sticky{
765 will-change: min-height;
765 will-change: min-height;
766 height: 80px;
766 height: 80px;
767 }
767 }
768
768
769 .sidebar__inner{
769 .sidebar__inner{
770 transform: translate(0, 0); /* For browsers don't support translate3d. */
770 transform: translate(0, 0); /* For browsers don't support translate3d. */
771 transform: translate3d(0, 0, 0);
771 transform: translate3d(0, 0, 0);
772 will-change: position, transform;
772 will-change: position, transform;
773 height: 65px;
773 height: 65px;
774 background-color: #fff;
774 background-color: #fff;
775 padding: 5px 0px;
775 padding: 5px 0px;
776 }
776 }
777
777
778 .sidebar__bar {
778 .sidebar__bar {
779 padding: 5px 0px 0px 0px
779 padding: 5px 0px 0px 0px
780 }
780 }
781
781
782 .fpath-placeholder {
782 .fpath-placeholder {
783 clear: both;
783 clear: both;
784 visibility: hidden
784 visibility: hidden
785 }
785 }
786
786
787 .is-affixed {
787 .is-affixed {
788
788
789 .sidebar__inner {
789 .sidebar__inner {
790 z-index: 30;
790 z-index: 30;
791 }
791 }
792
792
793 .sidebar_inner_shadow {
793 .sidebar_inner_shadow {
794 position: fixed;
794 position: fixed;
795 top: 75px;
795 top: 75px;
796 right: -100%;
796 right: -100%;
797 left: -100%;
797 left: -100%;
798 z-index: 30;
798 z-index: 30;
799 display: block;
799 display: block;
800 height: 5px;
800 height: 5px;
801 content: "";
801 content: "";
802 background: linear-gradient(rgba(0, 0, 0, 0.075), rgba(0, 0, 0, 0.001)) repeat-x 0 0;
802 background: linear-gradient(rgba(0, 0, 0, 0.075), rgba(0, 0, 0, 0.001)) repeat-x 0 0;
803 border-top: 1px solid rgba(0, 0, 0, 0.15);
803 border-top: 1px solid rgba(0, 0, 0, 0.15);
804 }
804 }
805
805
806 .fpath-placeholder {
806 .fpath-placeholder {
807 visibility: visible !important;
807 visibility: visible !important;
808 }
808 }
809 }
809 }
810
810
811 .diffset-menu {
811 .diffset-menu {
812
812
813 }
813 }
814
814
815 #todo-box {
815 #todo-box {
816 clear:both;
816 clear:both;
817 display: none;
817 display: none;
818 text-align: right
818 text-align: right
819 }
819 }
820
820
821 .diffset {
821 .diffset {
822 margin: 0px auto;
822 margin: 0px auto;
823 .diffset-heading {
823 .diffset-heading {
824 border: 1px solid @grey5;
824 border: 1px solid @grey5;
825 margin-bottom: -1px;
825 margin-bottom: -1px;
826 // margin-top: 20px;
826 // margin-top: 20px;
827 h2 {
827 h2 {
828 margin: 0;
828 margin: 0;
829 line-height: 38px;
829 line-height: 38px;
830 padding-left: 10px;
830 padding-left: 10px;
831 }
831 }
832 .btn {
832 .btn {
833 margin: 0;
833 margin: 0;
834 }
834 }
835 background: @grey6;
835 background: @grey6;
836 display: block;
836 display: block;
837 padding: 5px;
837 padding: 5px;
838 }
838 }
839 .diffset-heading-warning {
839 .diffset-heading-warning {
840 background: @alert3-inner;
840 background: @alert3-inner;
841 border: 1px solid @alert3;
841 border: 1px solid @alert3;
842 }
842 }
843 &.diffset-comments-disabled {
843 &.diffset-comments-disabled {
844 .cb-comment-box-opener, .comment-inline-form, .cb-comment-add-button {
844 .cb-comment-box-opener, .comment-inline-form, .cb-comment-add-button {
845 display: none !important;
845 display: none !important;
846 }
846 }
847 }
847 }
848 }
848 }
849
849
850 .filelist {
850 .filelist {
851 .pill {
851 .pill {
852 display: block;
852 display: block;
853 float: left;
853 float: left;
854 padding: @pill-padding-small;
854 padding: @pill-padding-small;
855 }
855 }
856 }
856 }
857
857
858 .pill {
858 .pill {
859 display: block;
859 display: block;
860 float: left;
860 float: left;
861 padding: @pill-padding;
861 padding: @pill-padding;
862 }
862 }
863
863
864 .pill-group {
864 .pill-group {
865 .pill {
865 .pill {
866 opacity: .8;
866 opacity: .8;
867 margin-right: 3px;
867 margin-right: 3px;
868 font-size: 12px;
868 font-size: 12px;
869 font-weight: normal;
869 font-weight: normal;
870 min-width: 30px;
870 min-width: 30px;
871 text-align: center;
871 text-align: center;
872
872
873 &:first-child {
873 &:first-child {
874 border-radius: @border-radius 0 0 @border-radius;
874 border-radius: @border-radius 0 0 @border-radius;
875 }
875 }
876 &:last-child {
876 &:last-child {
877 border-radius: 0 @border-radius @border-radius 0;
877 border-radius: 0 @border-radius @border-radius 0;
878 }
878 }
879 &:only-child {
879 &:only-child {
880 border-radius: @border-radius;
880 border-radius: @border-radius;
881 margin-right: 0;
881 margin-right: 0;
882 }
882 }
883 }
883 }
884 }
884 }
885
885
886 /* Main comments*/
886 /* Main comments*/
887 #comments {
887 #comments {
888 .comment-selected {
888 .comment-selected {
889 border-left: 6px solid @comment-highlight-color;
889 border-left: 6px solid @comment-highlight-color;
890 padding-left: 3px;
890 padding-left: 3px;
891 margin-left: -9px;
891 margin-left: -9px;
892 }
892 }
893 }
893 }
894
894
895 .filediff {
895 .filediff {
896 border: 1px solid @grey5;
896 border: 1px solid @grey5;
897
897
898 /* START OVERRIDES */
898 /* START OVERRIDES */
899 .code-highlight {
899 .code-highlight {
900 border: none; // TODO: remove this border from the global
900 border: none; // TODO: remove this border from the global
901 // .code-highlight, it doesn't belong there
901 // .code-highlight, it doesn't belong there
902 }
902 }
903 label {
903 label {
904 margin: 0; // TODO: remove this margin definition from global label
904 margin: 0; // TODO: remove this margin definition from global label
905 // it doesn't belong there - if margin on labels
905 // it doesn't belong there - if margin on labels
906 // are needed for a form they should be defined
906 // are needed for a form they should be defined
907 // in the form's class
907 // in the form's class
908 }
908 }
909 /* END OVERRIDES */
909 /* END OVERRIDES */
910
910
911 * {
911 * {
912 box-sizing: border-box;
912 box-sizing: border-box;
913 }
913 }
914
914
915 .on-hover-icon {
915 .on-hover-icon {
916 visibility: hidden;
916 visibility: hidden;
917 }
917 }
918
918
919 .filediff-anchor {
919 .filediff-anchor {
920 visibility: hidden;
920 visibility: hidden;
921 }
921 }
922 &:hover {
922 &:hover {
923 .filediff-anchor {
923 .filediff-anchor {
924 visibility: visible;
924 visibility: visible;
925 }
925 }
926 .on-hover-icon {
926 .on-hover-icon {
927 visibility: visible;
927 visibility: visible;
928 }
928 }
929 }
929 }
930
930
931 .filediff-heading {
931 .filediff-heading {
932 cursor: pointer;
932 cursor: pointer;
933 display: block;
933 display: block;
934 padding: 10px 10px;
934 padding: 10px 10px;
935 }
935 }
936 .filediff-heading:after {
936 .filediff-heading:after {
937 content: "";
937 content: "";
938 display: table;
938 display: table;
939 clear: both;
939 clear: both;
940 }
940 }
941 .filediff-heading:hover {
941 .filediff-heading:hover {
942 background: #e1e9f4 !important;
942 background: #e1e9f4 !important;
943 }
943 }
944
944
945 .filediff-menu {
945 .filediff-menu {
946 text-align: right;
946 text-align: right;
947 padding: 5px 5px 5px 0px;
947 padding: 5px 5px 5px 0px;
948 background: @grey7;
948 background: @grey7;
949
949
950 &> a,
950 &> a,
951 &> span {
951 &> span {
952 padding: 1px;
952 padding: 1px;
953 }
953 }
954 }
954 }
955
955
956 .filediff-collapse-button, .filediff-expand-button {
956 .filediff-collapse-button, .filediff-expand-button {
957 cursor: pointer;
957 cursor: pointer;
958 }
958 }
959 .filediff-collapse-button {
959 .filediff-collapse-button {
960 display: inline;
960 display: inline;
961 }
961 }
962 .filediff-expand-button {
962 .filediff-expand-button {
963 display: none;
963 display: none;
964 }
964 }
965 .filediff-collapsed .filediff-collapse-button {
965 .filediff-collapsed .filediff-collapse-button {
966 display: none;
966 display: none;
967 }
967 }
968 .filediff-collapsed .filediff-expand-button {
968 .filediff-collapsed .filediff-expand-button {
969 display: inline;
969 display: inline;
970 }
970 }
971
971
972 /**** COMMENTS ****/
972 /**** COMMENTS ****/
973
973
974 .filediff-menu {
974 .filediff-menu {
975 .show-comment-button {
975 .show-comment-button {
976 display: none;
976 display: none;
977 }
977 }
978 }
978 }
979 &.hide-comments {
979 &.hide-comments {
980 .inline-comments {
980 .inline-comments {
981 display: none;
981 display: none;
982 }
982 }
983 .filediff-menu {
983 .filediff-menu {
984 .show-comment-button {
984 .show-comment-button {
985 display: inline;
985 display: inline;
986 }
986 }
987 .hide-comment-button {
987 .hide-comment-button {
988 display: none;
988 display: none;
989 }
989 }
990 }
990 }
991 }
991 }
992
992
993 .hide-line-comments {
993 .hide-line-comments {
994 .inline-comments {
994 .inline-comments {
995 display: none;
995 display: none;
996 }
996 }
997 }
997 }
998
998
999 /**** END COMMENTS ****/
999 /**** END COMMENTS ****/
1000
1000
1001
1001
1002 .nav-chunk {
1002 .nav-chunk {
1003 position: absolute;
1003 position: absolute;
1004 right: 20px;
1004 right: 20px;
1005 margin-top: -17px;
1005 margin-top: -17px;
1006 }
1006 }
1007
1007
1008 .nav-chunk.selected {
1008 .nav-chunk.selected {
1009 visibility: visible !important;
1009 visibility: visible !important;
1010 }
1010 }
1011
1011
1012 #diff_nav {
1012 #diff_nav {
1013 color: @grey3;
1013 color: @grey3;
1014 }
1014 }
1015
1015
1016 }
1016 }
1017
1017
1018
1018
1019 .op-added {
1019 .op-added {
1020 color: @alert1;
1020 color: @alert1;
1021 }
1021 }
1022
1022
1023 .op-deleted {
1023 .op-deleted {
1024 color: @alert2;
1024 color: @alert2;
1025 }
1025 }
1026
1026
1027 .filediff, .filelist {
1027 .filediff, .filelist {
1028
1028
1029 .pill {
1029 .pill {
1030 &[op="name"] {
1030 &[op="name"] {
1031 background: none;
1031 background: none;
1032 opacity: 1;
1032 opacity: 1;
1033 color: white;
1033 color: white;
1034 }
1034 }
1035 &[op="limited"] {
1035 &[op="limited"] {
1036 background: @grey2;
1036 background: @grey2;
1037 color: white;
1037 color: white;
1038 }
1038 }
1039 &[op="binary"] {
1039 &[op="binary"] {
1040 background: @color7;
1040 background: @color7;
1041 color: white;
1041 color: white;
1042 }
1042 }
1043 &[op="modified"] {
1043 &[op="modified"] {
1044 background: @alert1;
1044 background: @alert1;
1045 color: white;
1045 color: white;
1046 }
1046 }
1047 &[op="renamed"] {
1047 &[op="renamed"] {
1048 background: @color4;
1048 background: @color4;
1049 color: white;
1049 color: white;
1050 }
1050 }
1051 &[op="copied"] {
1051 &[op="copied"] {
1052 background: @color4;
1052 background: @color4;
1053 color: white;
1053 color: white;
1054 }
1054 }
1055 &[op="mode"] {
1055 &[op="mode"] {
1056 background: @grey3;
1056 background: @grey3;
1057 color: white;
1057 color: white;
1058 }
1058 }
1059 &[op="symlink"] {
1059 &[op="symlink"] {
1060 background: @color8;
1060 background: @color8;
1061 color: white;
1061 color: white;
1062 }
1062 }
1063
1063
1064 &[op="added"] { /* added lines */
1064 &[op="added"] { /* added lines */
1065 background: @alert1;
1065 background: @alert1;
1066 color: white;
1066 color: white;
1067 }
1067 }
1068 &[op="deleted"] { /* deleted lines */
1068 &[op="deleted"] { /* deleted lines */
1069 background: @alert2;
1069 background: @alert2;
1070 color: white;
1070 color: white;
1071 }
1071 }
1072
1072
1073 &[op="created"] { /* created file */
1073 &[op="created"] { /* created file */
1074 background: @alert1;
1074 background: @alert1;
1075 color: white;
1075 color: white;
1076 }
1076 }
1077 &[op="removed"] { /* deleted file */
1077 &[op="removed"] { /* deleted file */
1078 background: @color5;
1078 background: @color5;
1079 color: white;
1079 color: white;
1080 }
1080 }
1081
1081 &[op="comments"] { /* comments on file */
1082 &[op="comments"] { /* comments on file */
1082 background: @grey4;
1083 background: @grey4;
1083 color: white;
1084 color: white;
1084 }
1085 }
1086
1087 &[op="options"] { /* context menu */
1088 background: @grey6;
1089 color: black;
1090 }
1085 }
1091 }
1086 }
1092 }
1087
1093
1088
1094
1089 .filediff-outdated {
1095 .filediff-outdated {
1090 padding: 8px 0;
1096 padding: 8px 0;
1091
1097
1092 .filediff-heading {
1098 .filediff-heading {
1093 opacity: .5;
1099 opacity: .5;
1094 }
1100 }
1095 }
1101 }
1096
1102
1097 table.cb {
1103 table.cb {
1098 width: 100%;
1104 width: 100%;
1099 border-collapse: collapse;
1105 border-collapse: collapse;
1100
1106
1101 .cb-text {
1107 .cb-text {
1102 padding: @cb-text-padding;
1108 padding: @cb-text-padding;
1103 }
1109 }
1104 .cb-hunk {
1110 .cb-hunk {
1105 padding: @cb-text-padding;
1111 padding: @cb-text-padding;
1106 }
1112 }
1107 .cb-expand {
1113 .cb-expand {
1108 display: none;
1114 display: none;
1109 }
1115 }
1110 .cb-collapse {
1116 .cb-collapse {
1111 display: inline;
1117 display: inline;
1112 }
1118 }
1113 &.cb-collapsed {
1119 &.cb-collapsed {
1114 .cb-line {
1120 .cb-line {
1115 display: none;
1121 display: none;
1116 }
1122 }
1117 .cb-expand {
1123 .cb-expand {
1118 display: inline;
1124 display: inline;
1119 }
1125 }
1120 .cb-collapse {
1126 .cb-collapse {
1121 display: none;
1127 display: none;
1122 }
1128 }
1123 .cb-hunk {
1129 .cb-hunk {
1124 display: none;
1130 display: none;
1125 }
1131 }
1126 }
1132 }
1127
1133
1128 /* intentionally general selector since .cb-line-selected must override it
1134 /* intentionally general selector since .cb-line-selected must override it
1129 and they both use !important since the td itself may have a random color
1135 and they both use !important since the td itself may have a random color
1130 generated by annotation blocks. TLDR: if you change it, make sure
1136 generated by annotation blocks. TLDR: if you change it, make sure
1131 annotated block selection and line selection in file view still work */
1137 annotated block selection and line selection in file view still work */
1132 .cb-line-fresh .cb-content {
1138 .cb-line-fresh .cb-content {
1133 background: white !important;
1139 background: white !important;
1134 }
1140 }
1135 .cb-warning {
1141 .cb-warning {
1136 background: #fff4dd;
1142 background: #fff4dd;
1137 }
1143 }
1138
1144
1139 &.cb-diff-sideside {
1145 &.cb-diff-sideside {
1140 td {
1146 td {
1141 &.cb-content {
1147 &.cb-content {
1142 width: 50%;
1148 width: 50%;
1143 }
1149 }
1144 }
1150 }
1145 }
1151 }
1146
1152
1147 tr {
1153 tr {
1148 &.cb-annotate {
1154 &.cb-annotate {
1149 border-top: 1px solid #eee;
1155 border-top: 1px solid #eee;
1150 }
1156 }
1151
1157
1152 &.cb-comment-info {
1158 &.cb-comment-info {
1153 border-top: 1px solid #eee;
1159 border-top: 1px solid #eee;
1154 color: rgba(0, 0, 0, 0.3);
1160 color: rgba(0, 0, 0, 0.3);
1155 background: #edf2f9;
1161 background: #edf2f9;
1156
1162
1157 td {
1163 td {
1158
1164
1159 }
1165 }
1160 }
1166 }
1161
1167
1162 &.cb-hunk {
1168 &.cb-hunk {
1163 font-family: @text-monospace;
1169 font-family: @text-monospace;
1164 color: rgba(0, 0, 0, 0.3);
1170 color: rgba(0, 0, 0, 0.3);
1165
1171
1166 td {
1172 td {
1167 &:first-child {
1173 &:first-child {
1168 background: #edf2f9;
1174 background: #edf2f9;
1169 }
1175 }
1170 &:last-child {
1176 &:last-child {
1171 background: #f4f7fb;
1177 background: #f4f7fb;
1172 }
1178 }
1173 }
1179 }
1174 }
1180 }
1175 }
1181 }
1176
1182
1177
1183
1178 td {
1184 td {
1179 vertical-align: top;
1185 vertical-align: top;
1180 padding: 0;
1186 padding: 0;
1181
1187
1182 &.cb-content {
1188 &.cb-content {
1183 font-size: 12.35px;
1189 font-size: 12.35px;
1184
1190
1185 &.cb-line-selected .cb-code {
1191 &.cb-line-selected .cb-code {
1186 background: @comment-highlight-color !important;
1192 background: @comment-highlight-color !important;
1187 }
1193 }
1188
1194
1189 span.cb-code {
1195 span.cb-code {
1190 line-height: @cb-line-height;
1196 line-height: @cb-line-height;
1191 padding-left: @cb-line-code-padding;
1197 padding-left: @cb-line-code-padding;
1192 padding-right: @cb-line-code-padding;
1198 padding-right: @cb-line-code-padding;
1193 display: block;
1199 display: block;
1194 white-space: pre-wrap;
1200 white-space: pre-wrap;
1195 font-family: @text-monospace;
1201 font-family: @text-monospace;
1196 word-break: break-all;
1202 word-break: break-all;
1197 .nonl {
1203 .nonl {
1198 color: @color5;
1204 color: @color5;
1199 }
1205 }
1200 .cb-action {
1206 .cb-action {
1201 &:before {
1207 &:before {
1202 content: " ";
1208 content: " ";
1203 }
1209 }
1204 &.cb-deletion:before {
1210 &.cb-deletion:before {
1205 content: "- ";
1211 content: "- ";
1206 }
1212 }
1207 &.cb-addition:before {
1213 &.cb-addition:before {
1208 content: "+ ";
1214 content: "+ ";
1209 }
1215 }
1210 }
1216 }
1211 }
1217 }
1212
1218
1213 &> button.cb-comment-box-opener {
1219 &> button.cb-comment-box-opener {
1214
1220
1215 padding: 2px 2px 1px 3px;
1221 padding: 2px 2px 1px 3px;
1216 margin-left: -6px;
1222 margin-left: -6px;
1217 margin-top: -1px;
1223 margin-top: -1px;
1218
1224
1219 border-radius: @border-radius;
1225 border-radius: @border-radius;
1220 position: absolute;
1226 position: absolute;
1221 display: none;
1227 display: none;
1222 }
1228 }
1223 .cb-comment {
1229 .cb-comment {
1224 margin-top: 10px;
1230 margin-top: 10px;
1225 white-space: normal;
1231 white-space: normal;
1226 }
1232 }
1227 }
1233 }
1228 &:hover {
1234 &:hover {
1229 button.cb-comment-box-opener {
1235 button.cb-comment-box-opener {
1230 display: block;
1236 display: block;
1231 }
1237 }
1232 &+ td button.cb-comment-box-opener {
1238 &+ td button.cb-comment-box-opener {
1233 display: block
1239 display: block
1234 }
1240 }
1235 }
1241 }
1236
1242
1237 &.cb-data {
1243 &.cb-data {
1238 text-align: right;
1244 text-align: right;
1239 width: 30px;
1245 width: 30px;
1240 font-family: @text-monospace;
1246 font-family: @text-monospace;
1241
1247
1242 .icon-comment {
1248 .icon-comment {
1243 cursor: pointer;
1249 cursor: pointer;
1244 }
1250 }
1245 &.cb-line-selected {
1251 &.cb-line-selected {
1246 background: @comment-highlight-color !important;
1252 background: @comment-highlight-color !important;
1247 }
1253 }
1248 &.cb-line-selected > div {
1254 &.cb-line-selected > div {
1249 display: block;
1255 display: block;
1250 background: @comment-highlight-color !important;
1256 background: @comment-highlight-color !important;
1251 line-height: @cb-line-height;
1257 line-height: @cb-line-height;
1252 color: rgba(0, 0, 0, 0.3);
1258 color: rgba(0, 0, 0, 0.3);
1253 }
1259 }
1254 }
1260 }
1255
1261
1256 &.cb-lineno {
1262 &.cb-lineno {
1257 padding: 0;
1263 padding: 0;
1258 width: 50px;
1264 width: 50px;
1259 color: rgba(0, 0, 0, 0.3);
1265 color: rgba(0, 0, 0, 0.3);
1260 text-align: right;
1266 text-align: right;
1261 border-right: 1px solid #eee;
1267 border-right: 1px solid #eee;
1262 font-family: @text-monospace;
1268 font-family: @text-monospace;
1263 -webkit-user-select: none;
1269 -webkit-user-select: none;
1264 -moz-user-select: none;
1270 -moz-user-select: none;
1265 user-select: none;
1271 user-select: none;
1266
1272
1267 a::before {
1273 a::before {
1268 content: attr(data-line-no);
1274 content: attr(data-line-no);
1269 }
1275 }
1270 &.cb-line-selected {
1276 &.cb-line-selected {
1271 background: @comment-highlight-color !important;
1277 background: @comment-highlight-color !important;
1272 }
1278 }
1273
1279
1274 a {
1280 a {
1275 display: block;
1281 display: block;
1276 padding-right: @cb-line-code-padding;
1282 padding-right: @cb-line-code-padding;
1277 padding-left: @cb-line-code-padding;
1283 padding-left: @cb-line-code-padding;
1278 line-height: @cb-line-height;
1284 line-height: @cb-line-height;
1279 color: rgba(0, 0, 0, 0.3);
1285 color: rgba(0, 0, 0, 0.3);
1280 }
1286 }
1281 }
1287 }
1282
1288
1283 &.cb-empty {
1289 &.cb-empty {
1284 background: @grey7;
1290 background: @grey7;
1285 }
1291 }
1286
1292
1287 ins {
1293 ins {
1288 color: black;
1294 color: black;
1289 background: #a6f3a6;
1295 background: #a6f3a6;
1290 text-decoration: none;
1296 text-decoration: none;
1291 }
1297 }
1292 del {
1298 del {
1293 color: black;
1299 color: black;
1294 background: #f8cbcb;
1300 background: #f8cbcb;
1295 text-decoration: none;
1301 text-decoration: none;
1296 }
1302 }
1297 &.cb-addition {
1303 &.cb-addition {
1298 background: #ecffec;
1304 background: #ecffec;
1299
1305
1300 &.blob-lineno {
1306 &.blob-lineno {
1301 background: #ddffdd;
1307 background: #ddffdd;
1302 }
1308 }
1303 }
1309 }
1304 &.cb-deletion {
1310 &.cb-deletion {
1305 background: #ffecec;
1311 background: #ffecec;
1306
1312
1307 &.blob-lineno {
1313 &.blob-lineno {
1308 background: #ffdddd;
1314 background: #ffdddd;
1309 }
1315 }
1310 }
1316 }
1311 &.cb-annotate-message-spacer {
1317 &.cb-annotate-message-spacer {
1312 width:8px;
1318 width:8px;
1313 padding: 1px 0px 0px 3px;
1319 padding: 1px 0px 0px 3px;
1314 }
1320 }
1315 &.cb-annotate-info {
1321 &.cb-annotate-info {
1316 width: 320px;
1322 width: 320px;
1317 min-width: 320px;
1323 min-width: 320px;
1318 max-width: 320px;
1324 max-width: 320px;
1319 padding: 5px 2px;
1325 padding: 5px 2px;
1320 font-size: 13px;
1326 font-size: 13px;
1321
1327
1322 .cb-annotate-message {
1328 .cb-annotate-message {
1323 padding: 2px 0px 0px 0px;
1329 padding: 2px 0px 0px 0px;
1324 white-space: pre-line;
1330 white-space: pre-line;
1325 overflow: hidden;
1331 overflow: hidden;
1326 }
1332 }
1327 .rc-user {
1333 .rc-user {
1328 float: none;
1334 float: none;
1329 padding: 0 6px 0 17px;
1335 padding: 0 6px 0 17px;
1330 min-width: unset;
1336 min-width: unset;
1331 min-height: unset;
1337 min-height: unset;
1332 }
1338 }
1333 }
1339 }
1334
1340
1335 &.cb-annotate-revision {
1341 &.cb-annotate-revision {
1336 cursor: pointer;
1342 cursor: pointer;
1337 text-align: right;
1343 text-align: right;
1338 padding: 1px 3px 0px 3px;
1344 padding: 1px 3px 0px 3px;
1339 }
1345 }
1340 }
1346 }
1341 }
1347 }
@@ -1,104 +1,108 b''
1 //--- RESETS ---//
1 //--- RESETS ---//
2 :focus { outline: none; }
2 :focus { outline: none; }
3 a { cursor: pointer; }
3 a { cursor: pointer; }
4
4
5 //--- clearfix --//
5 //--- clearfix --//
6 .clearfix {
6 .clearfix {
7 &:before,
7 &:before,
8 &:after {
8 &:after {
9 content:"";
9 content:"";
10 width: 100%;
10 width: 100%;
11 clear: both;
11 clear: both;
12 float: left;
12 float: left;
13 }
13 }
14 }
14 }
15
15
16 .clearinner:after { /* clears all floating divs inside a block */
16 .clearinner:after { /* clears all floating divs inside a block */
17 content: "";
17 content: "";
18 display: table;
18 display: table;
19 clear: both;
19 clear: both;
20 }
20 }
21
21
22 .js-template { /* mark a template for javascript use */
22 .js-template { /* mark a template for javascript use */
23 display: none;
23 display: none;
24 }
24 }
25
25
26 .linebreak {
26 .linebreak {
27 display: block;
27 display: block;
28 }
28 }
29
29
30 .clear-both {
30 .clear-both {
31 clear: both;
31 clear: both;
32 }
32 }
33
33
34 .display-none {
35 display: none;
36 }
37
34 .pull-right {
38 .pull-right {
35 float: right !important;
39 float: right !important;
36 }
40 }
37
41
38 .pull-left {
42 .pull-left {
39 float: left !important;
43 float: left !important;
40 }
44 }
41
45
42 .block-left {
46 .block-left {
43 float: left;
47 float: left;
44 }
48 }
45
49
46 .block-right {
50 .block-right {
47 float: right;
51 float: right;
48 clear: right;
52 clear: right;
49
53
50 li {
54 li {
51 list-style-type: none;
55 list-style-type: none;
52 }
56 }
53 }
57 }
54
58
55 .noselect {
59 .noselect {
56 -webkit-touch-callout: none; /* iOS Safari */
60 -webkit-touch-callout: none; /* iOS Safari */
57 -webkit-user-select: none; /* Safari */
61 -webkit-user-select: none; /* Safari */
58 -khtml-user-select: none; /* Konqueror HTML */
62 -khtml-user-select: none; /* Konqueror HTML */
59 -moz-user-select: none; /* Firefox */
63 -moz-user-select: none; /* Firefox */
60 -ms-user-select: none; /* Internet Explorer/Edge */
64 -ms-user-select: none; /* Internet Explorer/Edge */
61 user-select: none; /* Non-prefixed version, currently
65 user-select: none; /* Non-prefixed version, currently
62 supported by Chrome and Opera */
66 supported by Chrome and Opera */
63 }
67 }
64
68
65 //--- DEVICE-SPECIFIC CLASSES ---------------//
69 //--- DEVICE-SPECIFIC CLASSES ---------------//
66 //regular tablet and up
70 //regular tablet and up
67 @media (min-width:768px) {
71 @media (min-width:768px) {
68 .no-mobile {
72 .no-mobile {
69 display: block;
73 display: block;
70 }
74 }
71 .mobile-only {
75 .mobile-only {
72 display: none;
76 display: none;
73 }
77 }
74 }
78 }
75 //small tablet and phone
79 //small tablet and phone
76 @media (max-width:767px) {
80 @media (max-width:767px) {
77 .mobile-only {
81 .mobile-only {
78 display: block;
82 display: block;
79 }
83 }
80 .no-mobile {
84 .no-mobile {
81 display: none;
85 display: none;
82 }
86 }
83 }
87 }
84
88
85 //--- STICKY FOOTER ---//
89 //--- STICKY FOOTER ---//
86 html, body {
90 html, body {
87 height: 100%;
91 height: 100%;
88 margin: 0;
92 margin: 0;
89 }
93 }
90 .outerwrapper {
94 .outerwrapper {
91 height: 100%;
95 height: 100%;
92 min-height: 100%;
96 min-height: 100%;
93 margin: 0;
97 margin: 0;
94 padding-bottom: 3em; /* must be equal to footer height */
98 padding-bottom: 3em; /* must be equal to footer height */
95 }
99 }
96 .outerwrapper:after{
100 .outerwrapper:after{
97 content:" ";
101 content:" ";
98 }
102 }
99 #footer {
103 #footer {
100 clear: both;
104 clear: both;
101 position: relative;
105 position: relative;
102 height: 3em; /* footer height */
106 height: 3em; /* footer height */
103 margin: -3em 0 0; /* must be equal to footer height */
107 margin: -3em 0 0; /* must be equal to footer height */
104 }
108 }
@@ -1,3056 +1,3211 b''
1 //Primary CSS
1 //Primary CSS
2
2
3 //--- IMPORTS ------------------//
3 //--- IMPORTS ------------------//
4
4
5 @import 'helpers';
5 @import 'helpers';
6 @import 'mixins';
6 @import 'mixins';
7 @import 'rcicons';
7 @import 'rcicons';
8 @import 'variables';
8 @import 'variables';
9 @import 'bootstrap-variables';
9 @import 'bootstrap-variables';
10 @import 'form-bootstrap';
10 @import 'form-bootstrap';
11 @import 'codemirror';
11 @import 'codemirror';
12 @import 'legacy_code_styles';
12 @import 'legacy_code_styles';
13 @import 'readme-box';
13 @import 'readme-box';
14 @import 'progress-bar';
14 @import 'progress-bar';
15
15
16 @import 'type';
16 @import 'type';
17 @import 'alerts';
17 @import 'alerts';
18 @import 'buttons';
18 @import 'buttons';
19 @import 'tags';
19 @import 'tags';
20 @import 'code-block';
20 @import 'code-block';
21 @import 'examples';
21 @import 'examples';
22 @import 'login';
22 @import 'login';
23 @import 'main-content';
23 @import 'main-content';
24 @import 'select2';
24 @import 'select2';
25 @import 'comments';
25 @import 'comments';
26 @import 'panels-bootstrap';
26 @import 'panels-bootstrap';
27 @import 'panels';
27 @import 'panels';
28 @import 'deform';
28 @import 'deform';
29 @import 'tooltips';
29 @import 'tooltips';
30 @import 'sweetalert2';
30 @import 'sweetalert2';
31
31
32
32
33 //--- BASE ------------------//
33 //--- BASE ------------------//
34 .noscript-error {
34 .noscript-error {
35 top: 0;
35 top: 0;
36 left: 0;
36 left: 0;
37 width: 100%;
37 width: 100%;
38 z-index: 101;
38 z-index: 101;
39 text-align: center;
39 text-align: center;
40 font-size: 120%;
40 font-size: 120%;
41 color: white;
41 color: white;
42 background-color: @alert2;
42 background-color: @alert2;
43 padding: 5px 0 5px 0;
43 padding: 5px 0 5px 0;
44 font-weight: @text-semibold-weight;
44 font-weight: @text-semibold-weight;
45 font-family: @text-semibold;
45 font-family: @text-semibold;
46 }
46 }
47
47
48 html {
48 html {
49 display: table;
49 display: table;
50 height: 100%;
50 height: 100%;
51 width: 100%;
51 width: 100%;
52 }
52 }
53
53
54 body {
54 body {
55 display: table-cell;
55 display: table-cell;
56 width: 100%;
56 width: 100%;
57 }
57 }
58
58
59 //--- LAYOUT ------------------//
59 //--- LAYOUT ------------------//
60
60
61 .hidden{
61 .hidden{
62 display: none !important;
62 display: none !important;
63 }
63 }
64
64
65 .box{
65 .box{
66 float: left;
66 float: left;
67 width: 100%;
67 width: 100%;
68 }
68 }
69
69
70 .browser-header {
70 .browser-header {
71 clear: both;
71 clear: both;
72 }
72 }
73 .main {
73 .main {
74 clear: both;
74 clear: both;
75 padding:0 0 @pagepadding;
75 padding:0 0 @pagepadding;
76 height: auto;
76 height: auto;
77
77
78 &:after { //clearfix
78 &:after { //clearfix
79 content:"";
79 content:"";
80 clear:both;
80 clear:both;
81 width:100%;
81 width:100%;
82 display:block;
82 display:block;
83 }
83 }
84 }
84 }
85
85
86 .flex-container {
87 display: flex;
88 justify-content: space-between;
89 }
90
86 .action-link{
91 .action-link{
87 margin-left: @padding;
92 margin-left: @padding;
88 padding-left: @padding;
93 padding-left: @padding;
89 border-left: @border-thickness solid @border-default-color;
94 border-left: @border-thickness solid @border-default-color;
90 }
95 }
91
96
92 .cursor-pointer {
97 .cursor-pointer {
93 cursor: pointer;
98 cursor: pointer;
94 }
99 }
95
100
96 input + .action-link, .action-link.first{
101 input + .action-link, .action-link.first{
97 border-left: none;
102 border-left: none;
98 }
103 }
99
104
100 .link-disabled {
105 .link-disabled {
101 color: @grey4;
106 color: @grey4;
102 cursor: default;
107 cursor: default;
103 }
108 }
104
109
105 .action-link.last{
110 .action-link.last{
106 margin-right: @padding;
111 margin-right: @padding;
107 padding-right: @padding;
112 padding-right: @padding;
108 }
113 }
109
114
110 .action-link.active,
115 .action-link.active,
111 .action-link.active a{
116 .action-link.active a{
112 color: @grey4;
117 color: @grey4;
113 }
118 }
114
119
115 .action-link.disabled {
120 .action-link.disabled {
116 color: @grey4;
121 color: @grey4;
117 cursor: inherit;
122 cursor: inherit;
118 }
123 }
119
124
120 .grey-link-action {
125 .grey-link-action {
121 cursor: pointer;
126 cursor: pointer;
122 &:hover {
127 &:hover {
123 color: @grey2;
128 color: @grey2;
124 }
129 }
125 color: @grey4;
130 color: @grey4;
126 }
131 }
127
132
128 .clipboard-action {
133 .clipboard-action {
129 cursor: pointer;
134 cursor: pointer;
130 margin-left: 5px;
135 margin-left: 5px;
131
136
132 &:not(.no-grey) {
137 &:not(.no-grey) {
133
138
134 &:hover {
139 &:hover {
135 color: @grey2;
140 color: @grey2;
136 }
141 }
137 color: @grey4;
142 color: @grey4;
138 }
143 }
139 }
144 }
140
145
141 ul.simple-list{
146 ul.simple-list{
142 list-style: none;
147 list-style: none;
143 margin: 0;
148 margin: 0;
144 padding: 0;
149 padding: 0;
145 }
150 }
146
151
147 .main-content {
152 .main-content {
148 padding-bottom: @pagepadding;
153 padding-bottom: @pagepadding;
149 }
154 }
150
155
151 .wide-mode-wrapper {
156 .wide-mode-wrapper {
152 max-width:4000px !important;
157 max-width:4000px !important;
153 }
158 }
154
159
155 .wrapper {
160 .wrapper {
156 position: relative;
161 position: relative;
157 max-width: @wrapper-maxwidth;
162 max-width: @wrapper-maxwidth;
158 margin: 0 auto;
163 margin: 0 auto;
159 }
164 }
160
165
161 #content {
166 #content {
162 clear: both;
167 clear: both;
163 padding: 0 @contentpadding;
168 padding: 0 @contentpadding;
164 }
169 }
165
170
166 .advanced-settings-fields{
171 .advanced-settings-fields{
167 input{
172 input{
168 margin-left: @textmargin;
173 margin-left: @textmargin;
169 margin-right: @padding/2;
174 margin-right: @padding/2;
170 }
175 }
171 }
176 }
172
177
173 .cs_files_title {
178 .cs_files_title {
174 margin: @pagepadding 0 0;
179 margin: @pagepadding 0 0;
175 }
180 }
176
181
177 input.inline[type="file"] {
182 input.inline[type="file"] {
178 display: inline;
183 display: inline;
179 }
184 }
180
185
181 .error_page {
186 .error_page {
182 margin: 10% auto;
187 margin: 10% auto;
183
188
184 h1 {
189 h1 {
185 color: @grey2;
190 color: @grey2;
186 }
191 }
187
192
188 .alert {
193 .alert {
189 margin: @padding 0;
194 margin: @padding 0;
190 }
195 }
191
196
192 .error-branding {
197 .error-branding {
193 color: @grey4;
198 color: @grey4;
194 font-weight: @text-semibold-weight;
199 font-weight: @text-semibold-weight;
195 font-family: @text-semibold;
200 font-family: @text-semibold;
196 }
201 }
197
202
198 .error_message {
203 .error_message {
199 font-family: @text-regular;
204 font-family: @text-regular;
200 }
205 }
201
206
202 .sidebar {
207 .sidebar {
203 min-height: 275px;
208 min-height: 275px;
204 margin: 0;
209 margin: 0;
205 padding: 0 0 @sidebarpadding @sidebarpadding;
210 padding: 0 0 @sidebarpadding @sidebarpadding;
206 border: none;
211 border: none;
207 }
212 }
208
213
209 .main-content {
214 .main-content {
210 position: relative;
215 position: relative;
211 margin: 0 @sidebarpadding @sidebarpadding;
216 margin: 0 @sidebarpadding @sidebarpadding;
212 padding: 0 0 0 @sidebarpadding;
217 padding: 0 0 0 @sidebarpadding;
213 border-left: @border-thickness solid @grey5;
218 border-left: @border-thickness solid @grey5;
214
219
215 @media (max-width:767px) {
220 @media (max-width:767px) {
216 clear: both;
221 clear: both;
217 width: 100%;
222 width: 100%;
218 margin: 0;
223 margin: 0;
219 border: none;
224 border: none;
220 }
225 }
221 }
226 }
222
227
223 .inner-column {
228 .inner-column {
224 float: left;
229 float: left;
225 width: 29.75%;
230 width: 29.75%;
226 min-height: 150px;
231 min-height: 150px;
227 margin: @sidebarpadding 2% 0 0;
232 margin: @sidebarpadding 2% 0 0;
228 padding: 0 2% 0 0;
233 padding: 0 2% 0 0;
229 border-right: @border-thickness solid @grey5;
234 border-right: @border-thickness solid @grey5;
230
235
231 @media (max-width:767px) {
236 @media (max-width:767px) {
232 clear: both;
237 clear: both;
233 width: 100%;
238 width: 100%;
234 border: none;
239 border: none;
235 }
240 }
236
241
237 ul {
242 ul {
238 padding-left: 1.25em;
243 padding-left: 1.25em;
239 }
244 }
240
245
241 &:last-child {
246 &:last-child {
242 margin: @sidebarpadding 0 0;
247 margin: @sidebarpadding 0 0;
243 border: none;
248 border: none;
244 }
249 }
245
250
246 h4 {
251 h4 {
247 margin: 0 0 @padding;
252 margin: 0 0 @padding;
248 font-weight: @text-semibold-weight;
253 font-weight: @text-semibold-weight;
249 font-family: @text-semibold;
254 font-family: @text-semibold;
250 }
255 }
251 }
256 }
252 }
257 }
253 .error-page-logo {
258 .error-page-logo {
254 width: 130px;
259 width: 130px;
255 height: 160px;
260 height: 160px;
256 }
261 }
257
262
258 // HEADER
263 // HEADER
259 .header {
264 .header {
260
265
261 // TODO: johbo: Fix login pages, so that they work without a min-height
266 // TODO: johbo: Fix login pages, so that they work without a min-height
262 // for the header and then remove the min-height. I chose a smaller value
267 // for the header and then remove the min-height. I chose a smaller value
263 // intentionally here to avoid rendering issues in the main navigation.
268 // intentionally here to avoid rendering issues in the main navigation.
264 min-height: 49px;
269 min-height: 49px;
265 min-width: 1024px;
270 min-width: 1024px;
266
271
267 position: relative;
272 position: relative;
268 vertical-align: bottom;
273 vertical-align: bottom;
269 padding: 0 @header-padding;
274 padding: 0 @header-padding;
270 background-color: @grey1;
275 background-color: @grey1;
271 color: @grey5;
276 color: @grey5;
272
277
273 .title {
278 .title {
274 overflow: visible;
279 overflow: visible;
275 }
280 }
276
281
277 &:before,
282 &:before,
278 &:after {
283 &:after {
279 content: "";
284 content: "";
280 clear: both;
285 clear: both;
281 width: 100%;
286 width: 100%;
282 }
287 }
283
288
284 // TODO: johbo: Avoids breaking "Repositories" chooser
289 // TODO: johbo: Avoids breaking "Repositories" chooser
285 .select2-container .select2-choice .select2-arrow {
290 .select2-container .select2-choice .select2-arrow {
286 display: none;
291 display: none;
287 }
292 }
288 }
293 }
289
294
290 #header-inner {
295 #header-inner {
291 &.title {
296 &.title {
292 margin: 0;
297 margin: 0;
293 }
298 }
294 &:before,
299 &:before,
295 &:after {
300 &:after {
296 content: "";
301 content: "";
297 clear: both;
302 clear: both;
298 }
303 }
299 }
304 }
300
305
301 // Gists
306 // Gists
302 #files_data {
307 #files_data {
303 clear: both; //for firefox
308 clear: both; //for firefox
304 padding-top: 10px;
309 padding-top: 10px;
305 }
310 }
306
311
307 #gistid {
312 #gistid {
308 margin-right: @padding;
313 margin-right: @padding;
309 }
314 }
310
315
311 // Global Settings Editor
316 // Global Settings Editor
312 .textarea.editor {
317 .textarea.editor {
313 float: left;
318 float: left;
314 position: relative;
319 position: relative;
315 max-width: @texteditor-width;
320 max-width: @texteditor-width;
316
321
317 select {
322 select {
318 position: absolute;
323 position: absolute;
319 top:10px;
324 top:10px;
320 right:0;
325 right:0;
321 }
326 }
322
327
323 .CodeMirror {
328 .CodeMirror {
324 margin: 0;
329 margin: 0;
325 }
330 }
326
331
327 .help-block {
332 .help-block {
328 margin: 0 0 @padding;
333 margin: 0 0 @padding;
329 padding:.5em;
334 padding:.5em;
330 background-color: @grey6;
335 background-color: @grey6;
331 &.pre-formatting {
336 &.pre-formatting {
332 white-space: pre;
337 white-space: pre;
333 }
338 }
334 }
339 }
335 }
340 }
336
341
337 ul.auth_plugins {
342 ul.auth_plugins {
338 margin: @padding 0 @padding @legend-width;
343 margin: @padding 0 @padding @legend-width;
339 padding: 0;
344 padding: 0;
340
345
341 li {
346 li {
342 margin-bottom: @padding;
347 margin-bottom: @padding;
343 line-height: 1em;
348 line-height: 1em;
344 list-style-type: none;
349 list-style-type: none;
345
350
346 .auth_buttons .btn {
351 .auth_buttons .btn {
347 margin-right: @padding;
352 margin-right: @padding;
348 }
353 }
349
354
350 }
355 }
351 }
356 }
352
357
353
358
354 // My Account PR list
359 // My Account PR list
355
360
356 #show_closed {
361 #show_closed {
357 margin: 0 1em 0 0;
362 margin: 0 1em 0 0;
358 }
363 }
359
364
360 #pull_request_list_table {
365 #pull_request_list_table {
361 .closed {
366 .closed {
362 background-color: @grey6;
367 background-color: @grey6;
363 }
368 }
364
369
365 .state-creating,
370 .state-creating,
366 .state-updating,
371 .state-updating,
367 .state-merging
372 .state-merging
368 {
373 {
369 background-color: @grey6;
374 background-color: @grey6;
370 }
375 }
371
376
372 .td-status {
377 .td-status {
373 padding-left: .5em;
378 padding-left: .5em;
374 }
379 }
375 .log-container .truncate {
380 .log-container .truncate {
376 height: 2.75em;
381 height: 2.75em;
377 white-space: pre-line;
382 white-space: pre-line;
378 }
383 }
379 table.rctable .user {
384 table.rctable .user {
380 padding-left: 0;
385 padding-left: 0;
381 }
386 }
382 table.rctable {
387 table.rctable {
383 td.td-description,
388 td.td-description,
384 .rc-user {
389 .rc-user {
385 min-width: auto;
390 min-width: auto;
386 }
391 }
387 }
392 }
388 }
393 }
389
394
390 // Pull Requests
395 // Pull Requests
391
396
392 .pullrequests_section_head {
397 .pullrequests_section_head {
393 display: block;
398 display: block;
394 clear: both;
399 clear: both;
395 margin: @padding 0;
400 margin: @padding 0;
396 font-weight: @text-bold-weight;
401 font-weight: @text-bold-weight;
397 font-family: @text-bold;
402 font-family: @text-bold;
398 }
403 }
399
404
400 .pr-commit-flow {
405 .pr-commit-flow {
401 position: relative;
406 position: relative;
402 font-weight: 600;
407 font-weight: 600;
403
408
404 .tag {
409 .tag {
405 display: inline-block;
410 display: inline-block;
406 margin: 0 1em .5em 0;
411 margin: 0 1em .5em 0;
407 }
412 }
408
413
409 .clone-url {
414 .clone-url {
410 display: inline-block;
415 display: inline-block;
411 margin: 0 0 .5em 0;
416 margin: 0 0 .5em 0;
412 padding: 0;
417 padding: 0;
413 line-height: 1.2em;
418 line-height: 1.2em;
414 }
419 }
415 }
420 }
416
421
417 .pr-mergeinfo {
422 .pr-mergeinfo {
418 min-width: 95% !important;
423 min-width: 95% !important;
419 padding: 0 !important;
424 padding: 0 !important;
420 border: 0;
425 border: 0;
421 }
426 }
422 .pr-mergeinfo-copy {
427 .pr-mergeinfo-copy {
423 padding: 0 0;
428 padding: 0 0;
424 }
429 }
425
430
426 .pr-pullinfo {
431 .pr-pullinfo {
427 min-width: 95% !important;
432 min-width: 95% !important;
428 padding: 0 !important;
433 padding: 0 !important;
429 border: 0;
434 border: 0;
430 }
435 }
431 .pr-pullinfo-copy {
436 .pr-pullinfo-copy {
432 padding: 0 0;
437 padding: 0 0;
433 }
438 }
434
439
435 .pr-title-input {
440 .pr-title-input {
436 width: 100%;
441 width: 100%;
437 font-size: 18px;
442 font-size: 18px;
438 margin: 0 0 4px 0;
443 margin: 0 0 4px 0;
439 padding: 0;
444 padding: 0;
440 line-height: 1.7em;
445 line-height: 1.7em;
441 color: @text-color;
446 color: @text-color;
442 letter-spacing: .02em;
447 letter-spacing: .02em;
443 font-weight: @text-bold-weight;
448 font-weight: @text-bold-weight;
444 font-family: @text-bold;
449 font-family: @text-bold;
445
450
446 &:hover {
451 &:hover {
447 box-shadow: none;
452 box-shadow: none;
448 }
453 }
449 }
454 }
450
455
451 #pr-title {
456 #pr-title {
452 input {
457 input {
453 border: 1px transparent;
458 border: 1px transparent;
454 color: black;
459 color: black;
455 opacity: 1;
460 opacity: 1;
456 background: #fff;
461 background: #fff;
457 font-size: 18px;
462 font-size: 18px;
458 }
463 }
459 }
464 }
460
465
461 .pr-title-closed-tag {
466 .pr-title-closed-tag {
462 font-size: 16px;
467 font-size: 16px;
463 }
468 }
464
469
465 #pr-desc {
470 #pr-desc {
466 padding: 10px 0;
471 padding: 10px 0;
467
472
468 .markdown-block {
473 .markdown-block {
469 padding: 0;
474 padding: 0;
470 margin-bottom: -30px;
475 margin-bottom: -30px;
471 }
476 }
472 }
477 }
473
478
474 #pullrequest_title {
479 #pullrequest_title {
475 width: 100%;
480 width: 100%;
476 box-sizing: border-box;
481 box-sizing: border-box;
477 }
482 }
478
483
479 #pr_open_message {
484 #pr_open_message {
480 border: @border-thickness solid #fff;
485 border: @border-thickness solid #fff;
481 border-radius: @border-radius;
486 border-radius: @border-radius;
482 text-align: left;
487 text-align: left;
483 overflow: hidden;
488 overflow: hidden;
484 white-space: pre-line;
489 white-space: pre-line;
490 padding-top: 5px
491 }
492
493 #add_reviewer {
494 padding-top: 10px;
495 }
496
497 #add_reviewer_input {
498 padding-top: 10px
485 }
499 }
486
500
487 .pr-details-title-author-pref {
501 .pr-details-title-author-pref {
488 padding-right: 10px
502 padding-right: 10px
489 }
503 }
490
504
491 .label-pr-detail {
505 .label-pr-detail {
492 display: table-cell;
506 display: table-cell;
493 width: 120px;
507 width: 120px;
494 padding-top: 7.5px;
508 padding-top: 7.5px;
495 padding-bottom: 7.5px;
509 padding-bottom: 7.5px;
496 padding-right: 7.5px;
510 padding-right: 7.5px;
497 }
511 }
498
512
499 .source-details ul {
513 .source-details ul {
500 padding: 10px 16px;
514 padding: 10px 16px;
501 }
515 }
502
516
503 .source-details-action {
517 .source-details-action {
504 color: @grey4;
518 color: @grey4;
505 font-size: 11px
519 font-size: 11px
506 }
520 }
507
521
508 .pr-submit-button {
522 .pr-submit-button {
509 float: right;
523 float: right;
510 margin: 0 0 0 5px;
524 margin: 0 0 0 5px;
511 }
525 }
512
526
513 .pr-spacing-container {
527 .pr-spacing-container {
514 padding: 20px;
528 padding: 20px;
515 clear: both
529 clear: both
516 }
530 }
517
531
518 #pr-description-input {
532 #pr-description-input {
519 margin-bottom: 0;
533 margin-bottom: 0;
520 }
534 }
521
535
522 .pr-description-label {
536 .pr-description-label {
523 vertical-align: top;
537 vertical-align: top;
524 }
538 }
525
539
526 #open_edit_pullrequest {
540 #open_edit_pullrequest {
527 padding: 0;
541 padding: 0;
528 }
542 }
529
543
530 #close_edit_pullrequest {
544 #close_edit_pullrequest {
531
545
532 }
546 }
533
547
534 #delete_pullrequest {
548 #delete_pullrequest {
535 clear: inherit;
549 clear: inherit;
536
550
537 form {
551 form {
538 display: inline;
552 display: inline;
539 }
553 }
540
554
541 }
555 }
542
556
543 .perms_section_head {
557 .perms_section_head {
544 min-width: 625px;
558 min-width: 625px;
545
559
546 h2 {
560 h2 {
547 margin-bottom: 0;
561 margin-bottom: 0;
548 }
562 }
549
563
550 .label-checkbox {
564 .label-checkbox {
551 float: left;
565 float: left;
552 }
566 }
553
567
554 &.field {
568 &.field {
555 margin: @space 0 @padding;
569 margin: @space 0 @padding;
556 }
570 }
557
571
558 &:first-child.field {
572 &:first-child.field {
559 margin-top: 0;
573 margin-top: 0;
560
574
561 .label {
575 .label {
562 margin-top: 0;
576 margin-top: 0;
563 padding-top: 0;
577 padding-top: 0;
564 }
578 }
565
579
566 .radios {
580 .radios {
567 padding-top: 0;
581 padding-top: 0;
568 }
582 }
569 }
583 }
570
584
571 .radios {
585 .radios {
572 position: relative;
586 position: relative;
573 width: 505px;
587 width: 505px;
574 }
588 }
575 }
589 }
576
590
577 //--- MODULES ------------------//
591 //--- MODULES ------------------//
578
592
579
593
580 // Server Announcement
594 // Server Announcement
581 #server-announcement {
595 #server-announcement {
582 width: 95%;
596 width: 95%;
583 margin: @padding auto;
597 margin: @padding auto;
584 padding: @padding;
598 padding: @padding;
585 border-width: 2px;
599 border-width: 2px;
586 border-style: solid;
600 border-style: solid;
587 .border-radius(2px);
601 .border-radius(2px);
588 font-weight: @text-bold-weight;
602 font-weight: @text-bold-weight;
589 font-family: @text-bold;
603 font-family: @text-bold;
590
604
591 &.info { border-color: @alert4; background-color: @alert4-inner; }
605 &.info { border-color: @alert4; background-color: @alert4-inner; }
592 &.warning { border-color: @alert3; background-color: @alert3-inner; }
606 &.warning { border-color: @alert3; background-color: @alert3-inner; }
593 &.error { border-color: @alert2; background-color: @alert2-inner; }
607 &.error { border-color: @alert2; background-color: @alert2-inner; }
594 &.success { border-color: @alert1; background-color: @alert1-inner; }
608 &.success { border-color: @alert1; background-color: @alert1-inner; }
595 &.neutral { border-color: @grey3; background-color: @grey6; }
609 &.neutral { border-color: @grey3; background-color: @grey6; }
596 }
610 }
597
611
598 // Fixed Sidebar Column
612 // Fixed Sidebar Column
599 .sidebar-col-wrapper {
613 .sidebar-col-wrapper {
600 padding-left: @sidebar-all-width;
614 padding-left: @sidebar-all-width;
601
615
602 .sidebar {
616 .sidebar {
603 width: @sidebar-width;
617 width: @sidebar-width;
604 margin-left: -@sidebar-all-width;
618 margin-left: -@sidebar-all-width;
605 }
619 }
606 }
620 }
607
621
608 .sidebar-col-wrapper.scw-small {
622 .sidebar-col-wrapper.scw-small {
609 padding-left: @sidebar-small-all-width;
623 padding-left: @sidebar-small-all-width;
610
624
611 .sidebar {
625 .sidebar {
612 width: @sidebar-small-width;
626 width: @sidebar-small-width;
613 margin-left: -@sidebar-small-all-width;
627 margin-left: -@sidebar-small-all-width;
614 }
628 }
615 }
629 }
616
630
617
631
618 // FOOTER
632 // FOOTER
619 #footer {
633 #footer {
620 padding: 0;
634 padding: 0;
621 text-align: center;
635 text-align: center;
622 vertical-align: middle;
636 vertical-align: middle;
623 color: @grey2;
637 color: @grey2;
624 font-size: 11px;
638 font-size: 11px;
625
639
626 p {
640 p {
627 margin: 0;
641 margin: 0;
628 padding: 1em;
642 padding: 1em;
629 line-height: 1em;
643 line-height: 1em;
630 }
644 }
631
645
632 .server-instance { //server instance
646 .server-instance { //server instance
633 display: none;
647 display: none;
634 }
648 }
635
649
636 .title {
650 .title {
637 float: none;
651 float: none;
638 margin: 0 auto;
652 margin: 0 auto;
639 }
653 }
640 }
654 }
641
655
642 button.close {
656 button.close {
643 padding: 0;
657 padding: 0;
644 cursor: pointer;
658 cursor: pointer;
645 background: transparent;
659 background: transparent;
646 border: 0;
660 border: 0;
647 .box-shadow(none);
661 .box-shadow(none);
648 -webkit-appearance: none;
662 -webkit-appearance: none;
649 }
663 }
650
664
651 .close {
665 .close {
652 float: right;
666 float: right;
653 font-size: 21px;
667 font-size: 21px;
654 font-family: @text-bootstrap;
668 font-family: @text-bootstrap;
655 line-height: 1em;
669 line-height: 1em;
656 font-weight: bold;
670 font-weight: bold;
657 color: @grey2;
671 color: @grey2;
658
672
659 &:hover,
673 &:hover,
660 &:focus {
674 &:focus {
661 color: @grey1;
675 color: @grey1;
662 text-decoration: none;
676 text-decoration: none;
663 cursor: pointer;
677 cursor: pointer;
664 }
678 }
665 }
679 }
666
680
667 // GRID
681 // GRID
668 .sorting,
682 .sorting,
669 .sorting_desc,
683 .sorting_desc,
670 .sorting_asc {
684 .sorting_asc {
671 cursor: pointer;
685 cursor: pointer;
672 }
686 }
673 .sorting_desc:after {
687 .sorting_desc:after {
674 content: "\00A0\25B2";
688 content: "\00A0\25B2";
675 font-size: .75em;
689 font-size: .75em;
676 }
690 }
677 .sorting_asc:after {
691 .sorting_asc:after {
678 content: "\00A0\25BC";
692 content: "\00A0\25BC";
679 font-size: .68em;
693 font-size: .68em;
680 }
694 }
681
695
682
696
683 .user_auth_tokens {
697 .user_auth_tokens {
684
698
685 &.truncate {
699 &.truncate {
686 white-space: nowrap;
700 white-space: nowrap;
687 overflow: hidden;
701 overflow: hidden;
688 text-overflow: ellipsis;
702 text-overflow: ellipsis;
689 }
703 }
690
704
691 .fields .field .input {
705 .fields .field .input {
692 margin: 0;
706 margin: 0;
693 }
707 }
694
708
695 input#description {
709 input#description {
696 width: 100px;
710 width: 100px;
697 margin: 0;
711 margin: 0;
698 }
712 }
699
713
700 .drop-menu {
714 .drop-menu {
701 // TODO: johbo: Remove this, should work out of the box when
715 // TODO: johbo: Remove this, should work out of the box when
702 // having multiple inputs inline
716 // having multiple inputs inline
703 margin: 0 0 0 5px;
717 margin: 0 0 0 5px;
704 }
718 }
705 }
719 }
706 #user_list_table {
720 #user_list_table {
707 .closed {
721 .closed {
708 background-color: @grey6;
722 background-color: @grey6;
709 }
723 }
710 }
724 }
711
725
712
726
713 input, textarea {
727 input, textarea {
714 &.disabled {
728 &.disabled {
715 opacity: .5;
729 opacity: .5;
716 }
730 }
717
731
718 &:hover {
732 &:hover {
719 border-color: @grey3;
733 border-color: @grey3;
720 box-shadow: @button-shadow;
734 box-shadow: @button-shadow;
721 }
735 }
722
736
723 &:focus {
737 &:focus {
724 border-color: @rcblue;
738 border-color: @rcblue;
725 box-shadow: @button-shadow;
739 box-shadow: @button-shadow;
726 }
740 }
727 }
741 }
728
742
729 // remove extra padding in firefox
743 // remove extra padding in firefox
730 input::-moz-focus-inner { border:0; padding:0 }
744 input::-moz-focus-inner { border:0; padding:0 }
731
745
732 .adjacent input {
746 .adjacent input {
733 margin-bottom: @padding;
747 margin-bottom: @padding;
734 }
748 }
735
749
736 .permissions_boxes {
750 .permissions_boxes {
737 display: block;
751 display: block;
738 }
752 }
739
753
740 //FORMS
754 //FORMS
741
755
742 .medium-inline,
756 .medium-inline,
743 input#description.medium-inline {
757 input#description.medium-inline {
744 display: inline;
758 display: inline;
745 width: @medium-inline-input-width;
759 width: @medium-inline-input-width;
746 min-width: 100px;
760 min-width: 100px;
747 }
761 }
748
762
749 select {
763 select {
750 //reset
764 //reset
751 -webkit-appearance: none;
765 -webkit-appearance: none;
752 -moz-appearance: none;
766 -moz-appearance: none;
753
767
754 display: inline-block;
768 display: inline-block;
755 height: 28px;
769 height: 28px;
756 width: auto;
770 width: auto;
757 margin: 0 @padding @padding 0;
771 margin: 0 @padding @padding 0;
758 padding: 0 18px 0 8px;
772 padding: 0 18px 0 8px;
759 line-height:1em;
773 line-height:1em;
760 font-size: @basefontsize;
774 font-size: @basefontsize;
761 border: @border-thickness solid @grey5;
775 border: @border-thickness solid @grey5;
762 border-radius: @border-radius;
776 border-radius: @border-radius;
763 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
777 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
764 color: @grey4;
778 color: @grey4;
765 box-shadow: @button-shadow;
779 box-shadow: @button-shadow;
766
780
767 &:after {
781 &:after {
768 content: "\00A0\25BE";
782 content: "\00A0\25BE";
769 }
783 }
770
784
771 &:focus, &:hover {
785 &:focus, &:hover {
772 outline: none;
786 outline: none;
773 border-color: @grey4;
787 border-color: @grey4;
774 color: @rcdarkblue;
788 color: @rcdarkblue;
775 }
789 }
776 }
790 }
777
791
778 option {
792 option {
779 &:focus {
793 &:focus {
780 outline: none;
794 outline: none;
781 }
795 }
782 }
796 }
783
797
784 input,
798 input,
785 textarea {
799 textarea {
786 padding: @input-padding;
800 padding: @input-padding;
787 border: @input-border-thickness solid @border-highlight-color;
801 border: @input-border-thickness solid @border-highlight-color;
788 .border-radius (@border-radius);
802 .border-radius (@border-radius);
789 font-family: @text-light;
803 font-family: @text-light;
790 font-size: @basefontsize;
804 font-size: @basefontsize;
791
805
792 &.input-sm {
806 &.input-sm {
793 padding: 5px;
807 padding: 5px;
794 }
808 }
795
809
796 &#description {
810 &#description {
797 min-width: @input-description-minwidth;
811 min-width: @input-description-minwidth;
798 min-height: 1em;
812 min-height: 1em;
799 padding: 10px;
813 padding: 10px;
800 }
814 }
801 }
815 }
802
816
803 .field-sm {
817 .field-sm {
804 input,
818 input,
805 textarea {
819 textarea {
806 padding: 5px;
820 padding: 5px;
807 }
821 }
808 }
822 }
809
823
810 textarea {
824 textarea {
811 display: block;
825 display: block;
812 clear: both;
826 clear: both;
813 width: 100%;
827 width: 100%;
814 min-height: 100px;
828 min-height: 100px;
815 margin-bottom: @padding;
829 margin-bottom: @padding;
816 .box-sizing(border-box);
830 .box-sizing(border-box);
817 overflow: auto;
831 overflow: auto;
818 }
832 }
819
833
820 label {
834 label {
821 font-family: @text-light;
835 font-family: @text-light;
822 }
836 }
823
837
824 // GRAVATARS
838 // GRAVATARS
825 // centers gravatar on username to the right
839 // centers gravatar on username to the right
826
840
827 .gravatar {
841 .gravatar {
828 display: inline;
842 display: inline;
829 min-width: 16px;
843 min-width: 16px;
830 min-height: 16px;
844 min-height: 16px;
831 margin: -5px 0;
845 margin: -5px 0;
832 padding: 0;
846 padding: 0;
833 line-height: 1em;
847 line-height: 1em;
834 box-sizing: content-box;
848 box-sizing: content-box;
835 border-radius: 50%;
849 border-radius: 50%;
836
850
837 &.gravatar-large {
851 &.gravatar-large {
838 margin: -0.5em .25em -0.5em 0;
852 margin: -0.5em .25em -0.5em 0;
839 }
853 }
840
854
841 & + .user {
855 & + .user {
842 display: inline;
856 display: inline;
843 margin: 0;
857 margin: 0;
844 padding: 0 0 0 .17em;
858 padding: 0 0 0 .17em;
845 line-height: 1em;
859 line-height: 1em;
846 }
860 }
847
861
848 & + .no-margin {
862 & + .no-margin {
849 margin: 0
863 margin: 0
850 }
864 }
851
865
852 }
866 }
853
867
854 .user-inline-data {
868 .user-inline-data {
855 display: inline-block;
869 display: inline-block;
856 float: left;
870 float: left;
857 padding-left: .5em;
871 padding-left: .5em;
858 line-height: 1.3em;
872 line-height: 1.3em;
859 }
873 }
860
874
861 .rc-user { // gravatar + user wrapper
875 .rc-user { // gravatar + user wrapper
862 float: left;
876 float: left;
863 position: relative;
877 position: relative;
864 min-width: 100px;
878 min-width: 100px;
865 max-width: 200px;
879 max-width: 200px;
866 min-height: (@gravatar-size + @border-thickness * 2); // account for border
880 min-height: (@gravatar-size + @border-thickness * 2); // account for border
867 display: block;
881 display: block;
868 padding: 0 0 0 (@gravatar-size + @basefontsize/4);
882 padding: 0 0 0 (@gravatar-size + @basefontsize/4);
869
883
870
884
871 .gravatar {
885 .gravatar {
872 display: block;
886 display: block;
873 position: absolute;
887 position: absolute;
874 top: 0;
888 top: 0;
875 left: 0;
889 left: 0;
876 min-width: @gravatar-size;
890 min-width: @gravatar-size;
877 min-height: @gravatar-size;
891 min-height: @gravatar-size;
878 margin: 0;
892 margin: 0;
879 }
893 }
880
894
881 .user {
895 .user {
882 display: block;
896 display: block;
883 max-width: 175px;
897 max-width: 175px;
884 padding-top: 2px;
898 padding-top: 2px;
885 overflow: hidden;
899 overflow: hidden;
886 text-overflow: ellipsis;
900 text-overflow: ellipsis;
887 }
901 }
888 }
902 }
889
903
890 .gist-gravatar,
904 .gist-gravatar,
891 .journal_container {
905 .journal_container {
892 .gravatar-large {
906 .gravatar-large {
893 margin: 0 .5em -10px 0;
907 margin: 0 .5em -10px 0;
894 }
908 }
895 }
909 }
896
910
897 .gist-type-fields {
911 .gist-type-fields {
898 line-height: 30px;
912 line-height: 30px;
899 height: 30px;
913 height: 30px;
900
914
901 .gist-type-fields-wrapper {
915 .gist-type-fields-wrapper {
902 vertical-align: middle;
916 vertical-align: middle;
903 display: inline-block;
917 display: inline-block;
904 line-height: 25px;
918 line-height: 25px;
905 }
919 }
906 }
920 }
907
921
908 // ADMIN SETTINGS
922 // ADMIN SETTINGS
909
923
910 // Tag Patterns
924 // Tag Patterns
911 .tag_patterns {
925 .tag_patterns {
912 .tag_input {
926 .tag_input {
913 margin-bottom: @padding;
927 margin-bottom: @padding;
914 }
928 }
915 }
929 }
916
930
917 .locked_input {
931 .locked_input {
918 position: relative;
932 position: relative;
919
933
920 input {
934 input {
921 display: inline;
935 display: inline;
922 margin: 3px 5px 0px 0px;
936 margin: 3px 5px 0px 0px;
923 }
937 }
924
938
925 br {
939 br {
926 display: none;
940 display: none;
927 }
941 }
928
942
929 .error-message {
943 .error-message {
930 float: left;
944 float: left;
931 width: 100%;
945 width: 100%;
932 }
946 }
933
947
934 .lock_input_button {
948 .lock_input_button {
935 display: inline;
949 display: inline;
936 }
950 }
937
951
938 .help-block {
952 .help-block {
939 clear: both;
953 clear: both;
940 }
954 }
941 }
955 }
942
956
943 // Notifications
957 // Notifications
944
958
945 .notifications_buttons {
959 .notifications_buttons {
946 margin: 0 0 @space 0;
960 margin: 0 0 @space 0;
947 padding: 0;
961 padding: 0;
948
962
949 .btn {
963 .btn {
950 display: inline-block;
964 display: inline-block;
951 }
965 }
952 }
966 }
953
967
954 .notification-list {
968 .notification-list {
955
969
956 div {
970 div {
957 vertical-align: middle;
971 vertical-align: middle;
958 }
972 }
959
973
960 .container {
974 .container {
961 display: block;
975 display: block;
962 margin: 0 0 @padding 0;
976 margin: 0 0 @padding 0;
963 }
977 }
964
978
965 .delete-notifications {
979 .delete-notifications {
966 margin-left: @padding;
980 margin-left: @padding;
967 text-align: right;
981 text-align: right;
968 cursor: pointer;
982 cursor: pointer;
969 }
983 }
970
984
971 .read-notifications {
985 .read-notifications {
972 margin-left: @padding/2;
986 margin-left: @padding/2;
973 text-align: right;
987 text-align: right;
974 width: 35px;
988 width: 35px;
975 cursor: pointer;
989 cursor: pointer;
976 }
990 }
977
991
978 .icon-minus-sign {
992 .icon-minus-sign {
979 color: @alert2;
993 color: @alert2;
980 }
994 }
981
995
982 .icon-ok-sign {
996 .icon-ok-sign {
983 color: @alert1;
997 color: @alert1;
984 }
998 }
985 }
999 }
986
1000
987 .user_settings {
1001 .user_settings {
988 float: left;
1002 float: left;
989 clear: both;
1003 clear: both;
990 display: block;
1004 display: block;
991 width: 100%;
1005 width: 100%;
992
1006
993 .gravatar_box {
1007 .gravatar_box {
994 margin-bottom: @padding;
1008 margin-bottom: @padding;
995
1009
996 &:after {
1010 &:after {
997 content: " ";
1011 content: " ";
998 clear: both;
1012 clear: both;
999 width: 100%;
1013 width: 100%;
1000 }
1014 }
1001 }
1015 }
1002
1016
1003 .fields .field {
1017 .fields .field {
1004 clear: both;
1018 clear: both;
1005 }
1019 }
1006 }
1020 }
1007
1021
1008 .advanced_settings {
1022 .advanced_settings {
1009 margin-bottom: @space;
1023 margin-bottom: @space;
1010
1024
1011 .help-block {
1025 .help-block {
1012 margin-left: 0;
1026 margin-left: 0;
1013 }
1027 }
1014
1028
1015 button + .help-block {
1029 button + .help-block {
1016 margin-top: @padding;
1030 margin-top: @padding;
1017 }
1031 }
1018 }
1032 }
1019
1033
1020 // admin settings radio buttons and labels
1034 // admin settings radio buttons and labels
1021 .label-2 {
1035 .label-2 {
1022 float: left;
1036 float: left;
1023 width: @label2-width;
1037 width: @label2-width;
1024
1038
1025 label {
1039 label {
1026 color: @grey1;
1040 color: @grey1;
1027 }
1041 }
1028 }
1042 }
1029 .checkboxes {
1043 .checkboxes {
1030 float: left;
1044 float: left;
1031 width: @checkboxes-width;
1045 width: @checkboxes-width;
1032 margin-bottom: @padding;
1046 margin-bottom: @padding;
1033
1047
1034 .checkbox {
1048 .checkbox {
1035 width: 100%;
1049 width: 100%;
1036
1050
1037 label {
1051 label {
1038 margin: 0;
1052 margin: 0;
1039 padding: 0;
1053 padding: 0;
1040 }
1054 }
1041 }
1055 }
1042
1056
1043 .checkbox + .checkbox {
1057 .checkbox + .checkbox {
1044 display: inline-block;
1058 display: inline-block;
1045 }
1059 }
1046
1060
1047 label {
1061 label {
1048 margin-right: 1em;
1062 margin-right: 1em;
1049 }
1063 }
1050 }
1064 }
1051
1065
1052 // CHANGELOG
1066 // CHANGELOG
1053 .container_header {
1067 .container_header {
1054 float: left;
1068 float: left;
1055 display: block;
1069 display: block;
1056 width: 100%;
1070 width: 100%;
1057 margin: @padding 0 @padding;
1071 margin: @padding 0 @padding;
1058
1072
1059 #filter_changelog {
1073 #filter_changelog {
1060 float: left;
1074 float: left;
1061 margin-right: @padding;
1075 margin-right: @padding;
1062 }
1076 }
1063
1077
1064 .breadcrumbs_light {
1078 .breadcrumbs_light {
1065 display: inline-block;
1079 display: inline-block;
1066 }
1080 }
1067 }
1081 }
1068
1082
1069 .info_box {
1083 .info_box {
1070 float: right;
1084 float: right;
1071 }
1085 }
1072
1086
1073
1087
1074
1088
1075 #graph_content{
1089 #graph_content{
1076
1090
1077 // adjust for table headers so that graph renders properly
1091 // adjust for table headers so that graph renders properly
1078 // #graph_nodes padding - table cell padding
1092 // #graph_nodes padding - table cell padding
1079 padding-top: (@space - (@basefontsize * 2.4));
1093 padding-top: (@space - (@basefontsize * 2.4));
1080
1094
1081 &.graph_full_width {
1095 &.graph_full_width {
1082 width: 100%;
1096 width: 100%;
1083 max-width: 100%;
1097 max-width: 100%;
1084 }
1098 }
1085 }
1099 }
1086
1100
1087 #graph {
1101 #graph {
1088
1102
1089 .pagination-left {
1103 .pagination-left {
1090 float: left;
1104 float: left;
1091 clear: both;
1105 clear: both;
1092 }
1106 }
1093
1107
1094 .log-container {
1108 .log-container {
1095 max-width: 345px;
1109 max-width: 345px;
1096
1110
1097 .message{
1111 .message{
1098 max-width: 340px;
1112 max-width: 340px;
1099 }
1113 }
1100 }
1114 }
1101
1115
1102 .graph-col-wrapper {
1116 .graph-col-wrapper {
1103
1117
1104 #graph_nodes {
1118 #graph_nodes {
1105 width: 100px;
1119 width: 100px;
1106 position: absolute;
1120 position: absolute;
1107 left: 70px;
1121 left: 70px;
1108 z-index: -1;
1122 z-index: -1;
1109 }
1123 }
1110 }
1124 }
1111
1125
1112 .load-more-commits {
1126 .load-more-commits {
1113 text-align: center;
1127 text-align: center;
1114 }
1128 }
1115 .load-more-commits:hover {
1129 .load-more-commits:hover {
1116 background-color: @grey7;
1130 background-color: @grey7;
1117 }
1131 }
1118 .load-more-commits {
1132 .load-more-commits {
1119 a {
1133 a {
1120 display: block;
1134 display: block;
1121 }
1135 }
1122 }
1136 }
1123 }
1137 }
1124
1138
1125 .obsolete-toggle {
1139 .obsolete-toggle {
1126 line-height: 30px;
1140 line-height: 30px;
1127 margin-left: -15px;
1141 margin-left: -15px;
1128 }
1142 }
1129
1143
1130 #rev_range_container, #rev_range_clear, #rev_range_more {
1144 #rev_range_container, #rev_range_clear, #rev_range_more {
1131 margin-top: -5px;
1145 margin-top: -5px;
1132 margin-bottom: -5px;
1146 margin-bottom: -5px;
1133 }
1147 }
1134
1148
1135 #filter_changelog {
1149 #filter_changelog {
1136 float: left;
1150 float: left;
1137 }
1151 }
1138
1152
1139
1153
1140 //--- THEME ------------------//
1154 //--- THEME ------------------//
1141
1155
1142 #logo {
1156 #logo {
1143 float: left;
1157 float: left;
1144 margin: 9px 0 0 0;
1158 margin: 9px 0 0 0;
1145
1159
1146 .header {
1160 .header {
1147 background-color: transparent;
1161 background-color: transparent;
1148 }
1162 }
1149
1163
1150 a {
1164 a {
1151 display: inline-block;
1165 display: inline-block;
1152 }
1166 }
1153
1167
1154 img {
1168 img {
1155 height:30px;
1169 height:30px;
1156 }
1170 }
1157 }
1171 }
1158
1172
1159 .logo-wrapper {
1173 .logo-wrapper {
1160 float:left;
1174 float:left;
1161 }
1175 }
1162
1176
1163 .branding {
1177 .branding {
1164 float: left;
1178 float: left;
1165 padding: 9px 2px;
1179 padding: 9px 2px;
1166 line-height: 1em;
1180 line-height: 1em;
1167 font-size: @navigation-fontsize;
1181 font-size: @navigation-fontsize;
1168
1182
1169 a {
1183 a {
1170 color: @grey5
1184 color: @grey5
1171 }
1185 }
1172 @media screen and (max-width: 1200px) {
1186
1187 // 1024px or smaller
1188 @media screen and (max-width: 1180px) {
1173 display: none;
1189 display: none;
1174 }
1190 }
1191
1175 }
1192 }
1176
1193
1177 img {
1194 img {
1178 border: none;
1195 border: none;
1179 outline: none;
1196 outline: none;
1180 }
1197 }
1181 user-profile-header
1198 user-profile-header
1182 label {
1199 label {
1183
1200
1184 input[type="checkbox"] {
1201 input[type="checkbox"] {
1185 margin-right: 1em;
1202 margin-right: 1em;
1186 }
1203 }
1187 input[type="radio"] {
1204 input[type="radio"] {
1188 margin-right: 1em;
1205 margin-right: 1em;
1189 }
1206 }
1190 }
1207 }
1191
1208
1192 .review-status {
1209 .review-status {
1193 &.under_review {
1210 &.under_review {
1194 color: @alert3;
1211 color: @alert3;
1195 }
1212 }
1196 &.approved {
1213 &.approved {
1197 color: @alert1;
1214 color: @alert1;
1198 }
1215 }
1199 &.rejected,
1216 &.rejected,
1200 &.forced_closed{
1217 &.forced_closed{
1201 color: @alert2;
1218 color: @alert2;
1202 }
1219 }
1203 &.not_reviewed {
1220 &.not_reviewed {
1204 color: @grey5;
1221 color: @grey5;
1205 }
1222 }
1206 }
1223 }
1207
1224
1208 .review-status-under_review {
1225 .review-status-under_review {
1209 color: @alert3;
1226 color: @alert3;
1210 }
1227 }
1211 .status-tag-under_review {
1228 .status-tag-under_review {
1212 border-color: @alert3;
1229 border-color: @alert3;
1213 }
1230 }
1214
1231
1215 .review-status-approved {
1232 .review-status-approved {
1216 color: @alert1;
1233 color: @alert1;
1217 }
1234 }
1218 .status-tag-approved {
1235 .status-tag-approved {
1219 border-color: @alert1;
1236 border-color: @alert1;
1220 }
1237 }
1221
1238
1222 .review-status-rejected,
1239 .review-status-rejected,
1223 .review-status-forced_closed {
1240 .review-status-forced_closed {
1224 color: @alert2;
1241 color: @alert2;
1225 }
1242 }
1226 .status-tag-rejected,
1243 .status-tag-rejected,
1227 .status-tag-forced_closed {
1244 .status-tag-forced_closed {
1228 border-color: @alert2;
1245 border-color: @alert2;
1229 }
1246 }
1230
1247
1231 .review-status-not_reviewed {
1248 .review-status-not_reviewed {
1232 color: @grey5;
1249 color: @grey5;
1233 }
1250 }
1234 .status-tag-not_reviewed {
1251 .status-tag-not_reviewed {
1235 border-color: @grey5;
1252 border-color: @grey5;
1236 }
1253 }
1237
1254
1238 .test_pattern_preview {
1255 .test_pattern_preview {
1239 margin: @space 0;
1256 margin: @space 0;
1240
1257
1241 p {
1258 p {
1242 margin-bottom: 0;
1259 margin-bottom: 0;
1243 border-bottom: @border-thickness solid @border-default-color;
1260 border-bottom: @border-thickness solid @border-default-color;
1244 color: @grey3;
1261 color: @grey3;
1245 }
1262 }
1246
1263
1247 .btn {
1264 .btn {
1248 margin-bottom: @padding;
1265 margin-bottom: @padding;
1249 }
1266 }
1250 }
1267 }
1251 #test_pattern_result {
1268 #test_pattern_result {
1252 display: none;
1269 display: none;
1253 &:extend(pre);
1270 &:extend(pre);
1254 padding: .9em;
1271 padding: .9em;
1255 color: @grey3;
1272 color: @grey3;
1256 background-color: @grey7;
1273 background-color: @grey7;
1257 border-right: @border-thickness solid @border-default-color;
1274 border-right: @border-thickness solid @border-default-color;
1258 border-bottom: @border-thickness solid @border-default-color;
1275 border-bottom: @border-thickness solid @border-default-color;
1259 border-left: @border-thickness solid @border-default-color;
1276 border-left: @border-thickness solid @border-default-color;
1260 }
1277 }
1261
1278
1262 #repo_vcs_settings {
1279 #repo_vcs_settings {
1263 #inherit_overlay_vcs_default {
1280 #inherit_overlay_vcs_default {
1264 display: none;
1281 display: none;
1265 }
1282 }
1266 #inherit_overlay_vcs_custom {
1283 #inherit_overlay_vcs_custom {
1267 display: custom;
1284 display: custom;
1268 }
1285 }
1269 &.inherited {
1286 &.inherited {
1270 #inherit_overlay_vcs_default {
1287 #inherit_overlay_vcs_default {
1271 display: block;
1288 display: block;
1272 }
1289 }
1273 #inherit_overlay_vcs_custom {
1290 #inherit_overlay_vcs_custom {
1274 display: none;
1291 display: none;
1275 }
1292 }
1276 }
1293 }
1277 }
1294 }
1278
1295
1279 .issue-tracker-link {
1296 .issue-tracker-link {
1280 color: @rcblue;
1297 color: @rcblue;
1281 }
1298 }
1282
1299
1283 // Issue Tracker Table Show/Hide
1300 // Issue Tracker Table Show/Hide
1284 #repo_issue_tracker {
1301 #repo_issue_tracker {
1285 #inherit_overlay {
1302 #inherit_overlay {
1286 display: none;
1303 display: none;
1287 }
1304 }
1288 #custom_overlay {
1305 #custom_overlay {
1289 display: custom;
1306 display: custom;
1290 }
1307 }
1291 &.inherited {
1308 &.inherited {
1292 #inherit_overlay {
1309 #inherit_overlay {
1293 display: block;
1310 display: block;
1294 }
1311 }
1295 #custom_overlay {
1312 #custom_overlay {
1296 display: none;
1313 display: none;
1297 }
1314 }
1298 }
1315 }
1299 }
1316 }
1300 table.issuetracker {
1317 table.issuetracker {
1301 &.readonly {
1318 &.readonly {
1302 tr, td {
1319 tr, td {
1303 color: @grey3;
1320 color: @grey3;
1304 }
1321 }
1305 }
1322 }
1306 .edit {
1323 .edit {
1307 display: none;
1324 display: none;
1308 }
1325 }
1309 .editopen {
1326 .editopen {
1310 .edit {
1327 .edit {
1311 display: inline;
1328 display: inline;
1312 }
1329 }
1313 .entry {
1330 .entry {
1314 display: none;
1331 display: none;
1315 }
1332 }
1316 }
1333 }
1317 tr td.td-action {
1334 tr td.td-action {
1318 min-width: 117px;
1335 min-width: 117px;
1319 }
1336 }
1320 td input {
1337 td input {
1321 max-width: none;
1338 max-width: none;
1322 min-width: 30px;
1339 min-width: 30px;
1323 width: 80%;
1340 width: 80%;
1324 }
1341 }
1325 .issuetracker_pref input {
1342 .issuetracker_pref input {
1326 width: 40%;
1343 width: 40%;
1327 }
1344 }
1328 input.edit_issuetracker_update {
1345 input.edit_issuetracker_update {
1329 margin-right: 0;
1346 margin-right: 0;
1330 width: auto;
1347 width: auto;
1331 }
1348 }
1332 }
1349 }
1333
1350
1334 table.integrations {
1351 table.integrations {
1335 .td-icon {
1352 .td-icon {
1336 width: 20px;
1353 width: 20px;
1337 .integration-icon {
1354 .integration-icon {
1338 height: 20px;
1355 height: 20px;
1339 width: 20px;
1356 width: 20px;
1340 }
1357 }
1341 }
1358 }
1342 }
1359 }
1343
1360
1344 .integrations {
1361 .integrations {
1345 a.integration-box {
1362 a.integration-box {
1346 color: @text-color;
1363 color: @text-color;
1347 &:hover {
1364 &:hover {
1348 .panel {
1365 .panel {
1349 background: #fbfbfb;
1366 background: #fbfbfb;
1350 }
1367 }
1351 }
1368 }
1352 .integration-icon {
1369 .integration-icon {
1353 width: 30px;
1370 width: 30px;
1354 height: 30px;
1371 height: 30px;
1355 margin-right: 20px;
1372 margin-right: 20px;
1356 float: left;
1373 float: left;
1357 }
1374 }
1358
1375
1359 .panel-body {
1376 .panel-body {
1360 padding: 10px;
1377 padding: 10px;
1361 }
1378 }
1362 .panel {
1379 .panel {
1363 margin-bottom: 10px;
1380 margin-bottom: 10px;
1364 }
1381 }
1365 h2 {
1382 h2 {
1366 display: inline-block;
1383 display: inline-block;
1367 margin: 0;
1384 margin: 0;
1368 min-width: 140px;
1385 min-width: 140px;
1369 }
1386 }
1370 }
1387 }
1371 a.integration-box.dummy-integration {
1388 a.integration-box.dummy-integration {
1372 color: @grey4
1389 color: @grey4
1373 }
1390 }
1374 }
1391 }
1375
1392
1376 //Permissions Settings
1393 //Permissions Settings
1377 #add_perm {
1394 #add_perm {
1378 margin: 0 0 @padding;
1395 margin: 0 0 @padding;
1379 cursor: pointer;
1396 cursor: pointer;
1380 }
1397 }
1381
1398
1382 .perm_ac {
1399 .perm_ac {
1383 input {
1400 input {
1384 width: 95%;
1401 width: 95%;
1385 }
1402 }
1386 }
1403 }
1387
1404
1388 .autocomplete-suggestions {
1405 .autocomplete-suggestions {
1389 width: auto !important; // overrides autocomplete.js
1406 width: auto !important; // overrides autocomplete.js
1390 min-width: 278px;
1407 min-width: 278px;
1391 margin: 0;
1408 margin: 0;
1392 border: @border-thickness solid @grey5;
1409 border: @border-thickness solid @grey5;
1393 border-radius: @border-radius;
1410 border-radius: @border-radius;
1394 color: @grey2;
1411 color: @grey2;
1395 background-color: white;
1412 background-color: white;
1396 }
1413 }
1397
1414
1398 .autocomplete-qfilter-suggestions {
1415 .autocomplete-qfilter-suggestions {
1399 width: auto !important; // overrides autocomplete.js
1416 width: auto !important; // overrides autocomplete.js
1400 max-height: 100% !important;
1417 max-height: 100% !important;
1401 min-width: 376px;
1418 min-width: 376px;
1402 margin: 0;
1419 margin: 0;
1403 border: @border-thickness solid @grey5;
1420 border: @border-thickness solid @grey5;
1404 color: @grey2;
1421 color: @grey2;
1405 background-color: white;
1422 background-color: white;
1406 }
1423 }
1407
1424
1408 .autocomplete-selected {
1425 .autocomplete-selected {
1409 background: #F0F0F0;
1426 background: #F0F0F0;
1410 }
1427 }
1411
1428
1412 .ac-container-wrap {
1429 .ac-container-wrap {
1413 margin: 0;
1430 margin: 0;
1414 padding: 8px;
1431 padding: 8px;
1415 border-bottom: @border-thickness solid @grey5;
1432 border-bottom: @border-thickness solid @grey5;
1416 list-style-type: none;
1433 list-style-type: none;
1417 cursor: pointer;
1434 cursor: pointer;
1418
1435
1419 &:hover {
1436 &:hover {
1420 background-color: @grey7;
1437 background-color: @grey7;
1421 }
1438 }
1422
1439
1423 img {
1440 img {
1424 height: @gravatar-size;
1441 height: @gravatar-size;
1425 width: @gravatar-size;
1442 width: @gravatar-size;
1426 margin-right: 1em;
1443 margin-right: 1em;
1427 }
1444 }
1428
1445
1429 strong {
1446 strong {
1430 font-weight: normal;
1447 font-weight: normal;
1431 }
1448 }
1432 }
1449 }
1433
1450
1434 // Settings Dropdown
1451 // Settings Dropdown
1435 .user-menu .container {
1452 .user-menu .container {
1436 padding: 0 4px;
1453 padding: 0 4px;
1437 margin: 0;
1454 margin: 0;
1438 }
1455 }
1439
1456
1440 .user-menu .gravatar {
1457 .user-menu .gravatar {
1441 cursor: pointer;
1458 cursor: pointer;
1442 }
1459 }
1443
1460
1444 .codeblock {
1461 .codeblock {
1445 margin-bottom: @padding;
1462 margin-bottom: @padding;
1446 clear: both;
1463 clear: both;
1447
1464
1448 .stats {
1465 .stats {
1449 overflow: hidden;
1466 overflow: hidden;
1450 }
1467 }
1451
1468
1452 .message{
1469 .message{
1453 textarea{
1470 textarea{
1454 margin: 0;
1471 margin: 0;
1455 }
1472 }
1456 }
1473 }
1457
1474
1458 .code-header {
1475 .code-header {
1459 .stats {
1476 .stats {
1460 line-height: 2em;
1477 line-height: 2em;
1461
1478
1462 .revision_id {
1479 .revision_id {
1463 margin-left: 0;
1480 margin-left: 0;
1464 }
1481 }
1465 .buttons {
1482 .buttons {
1466 padding-right: 0;
1483 padding-right: 0;
1467 }
1484 }
1468 }
1485 }
1469
1486
1470 .item{
1487 .item{
1471 margin-right: 0.5em;
1488 margin-right: 0.5em;
1472 }
1489 }
1473 }
1490 }
1474
1491
1475 #editor_container {
1492 #editor_container {
1476 position: relative;
1493 position: relative;
1477 margin: @padding 10px;
1494 margin: @padding 10px;
1478 }
1495 }
1479 }
1496 }
1480
1497
1481 #file_history_container {
1498 #file_history_container {
1482 display: none;
1499 display: none;
1483 }
1500 }
1484
1501
1485 .file-history-inner {
1502 .file-history-inner {
1486 margin-bottom: 10px;
1503 margin-bottom: 10px;
1487 }
1504 }
1488
1505
1489 // Pull Requests
1506 // Pull Requests
1490 .summary-details {
1507 .summary-details {
1491 width: 100%;
1508 width: 100%;
1492 }
1509 }
1493 .pr-summary {
1510 .pr-summary {
1494 border-bottom: @border-thickness solid @grey5;
1511 border-bottom: @border-thickness solid @grey5;
1495 margin-bottom: @space;
1512 margin-bottom: @space;
1496 }
1513 }
1497
1514
1498 .reviewers {
1515 .reviewers {
1499 width: 98%;
1516 width: 98%;
1500 }
1517 }
1501
1518
1502 .reviewers ul li {
1519 .reviewers ul li {
1503 position: relative;
1520 position: relative;
1504 width: 100%;
1521 width: 100%;
1505 padding-bottom: 8px;
1522 padding-bottom: 8px;
1506 list-style-type: none;
1523 list-style-type: none;
1507 }
1524 }
1508
1525
1509 .reviewer_entry {
1526 .reviewer_entry {
1510 min-height: 55px;
1527 min-height: 55px;
1511 }
1528 }
1512
1529
1513 .reviewer_reason {
1530 .reviewer_reason {
1514 padding-left: 20px;
1531 padding-left: 20px;
1515 line-height: 1.5em;
1532 line-height: 1.5em;
1516 }
1533 }
1517 .reviewer_status {
1534 .reviewer_status {
1518 display: inline-block;
1535 display: inline-block;
1519 width: 20px;
1536 width: 20px;
1520 min-width: 20px;
1537 min-width: 20px;
1521 height: 1.2em;
1538 height: 1.2em;
1522 line-height: 1em;
1539 line-height: 1em;
1523 }
1540 }
1524
1541
1525 .reviewer_name {
1542 .reviewer_name {
1526 display: inline-block;
1543 display: inline-block;
1527 max-width: 83%;
1544 max-width: 83%;
1528 padding-right: 20px;
1545 padding-right: 20px;
1529 vertical-align: middle;
1546 vertical-align: middle;
1530 line-height: 1;
1547 line-height: 1;
1531
1548
1532 .rc-user {
1549 .rc-user {
1533 min-width: 0;
1550 min-width: 0;
1534 margin: -2px 1em 0 0;
1551 margin: -2px 1em 0 0;
1535 }
1552 }
1536
1553
1537 .reviewer {
1554 .reviewer {
1538 float: left;
1555 float: left;
1539 }
1556 }
1540 }
1557 }
1541
1558
1542 .reviewer_member_mandatory {
1559 .reviewer_member_mandatory {
1543 width: 16px;
1560 width: 16px;
1544 font-size: 11px;
1561 font-size: 11px;
1545 margin: 0;
1562 margin: 0;
1546 padding: 0;
1563 padding: 0;
1547 color: black;
1564 color: black;
1548 opacity: 0.4;
1565 opacity: 0.4;
1549 }
1566 }
1550
1567
1551 .reviewer_member_mandatory_remove,
1568 .reviewer_member_mandatory_remove,
1552 .reviewer_member_remove {
1569 .reviewer_member_remove {
1553 width: 16px;
1570 width: 16px;
1554 padding: 0;
1571 padding: 0;
1555 color: black;
1572 color: black;
1573 cursor: pointer;
1556 }
1574 }
1557
1575
1558 .reviewer_member_mandatory_remove {
1576 .reviewer_member_mandatory_remove {
1559 color: @grey4;
1577 color: @grey4;
1560 }
1578 }
1561
1579
1562 .reviewer_member_status {
1580 .reviewer_member_status {
1563 margin-top: 5px;
1581 margin-top: 5px;
1564 }
1582 }
1565 .pr-summary #summary{
1583 .pr-summary #summary{
1566 width: 100%;
1584 width: 100%;
1567 }
1585 }
1568 .pr-summary .action_button:hover {
1586 .pr-summary .action_button:hover {
1569 border: 0;
1587 border: 0;
1570 cursor: pointer;
1588 cursor: pointer;
1571 }
1589 }
1572 .pr-details-title {
1590 .pr-details-title {
1573 height: 20px;
1591 height: 20px;
1574 line-height: 20px;
1592 line-height: 20px;
1575
1593
1576 padding-bottom: 8px;
1594 padding-bottom: 8px;
1577 border-bottom: @border-thickness solid @grey5;
1595 border-bottom: @border-thickness solid @grey5;
1578
1596
1579 .action_button.disabled {
1597 .action_button.disabled {
1580 color: @grey4;
1598 color: @grey4;
1581 cursor: inherit;
1599 cursor: inherit;
1582 }
1600 }
1583 .action_button {
1601 .action_button {
1584 color: @rcblue;
1602 color: @rcblue;
1585 }
1603 }
1586 }
1604 }
1587 .pr-details-content {
1605 .pr-details-content {
1588 margin-top: @textmargin - 5;
1606 margin-top: @textmargin - 5;
1589 margin-bottom: @textmargin - 5;
1607 margin-bottom: @textmargin - 5;
1590 }
1608 }
1591
1609
1592 .pr-reviewer-rules {
1610 .pr-reviewer-rules {
1593 padding: 10px 0px 20px 0px;
1611 padding: 10px 0px 20px 0px;
1594 }
1612 }
1595
1613
1596 .todo-resolved {
1614 .todo-resolved {
1597 text-decoration: line-through;
1615 text-decoration: line-through;
1598 }
1616 }
1599
1617
1600 .todo-table, .comments-table {
1618 .todo-table, .comments-table {
1601 width: 100%;
1619 width: 100%;
1602
1620
1603 td {
1621 td {
1604 padding: 5px 0px;
1622 padding: 5px 0px;
1605 }
1623 }
1606
1624
1607 .td-todo-number {
1625 .td-todo-number {
1608 text-align: left;
1626 text-align: left;
1609 white-space: nowrap;
1627 white-space: nowrap;
1610 width: 1%;
1628 width: 1%;
1611 padding-right: 2px;
1629 padding-right: 2px;
1612 }
1630 }
1613
1631
1614 .td-todo-gravatar {
1632 .td-todo-gravatar {
1615 width: 5%;
1633 width: 5%;
1616
1634
1617 img {
1635 img {
1618 margin: -3px 0;
1636 margin: -3px 0;
1619 }
1637 }
1620 }
1638 }
1621
1639
1622 }
1640 }
1623
1641
1624 .todo-comment-text-wrapper {
1642 .todo-comment-text-wrapper {
1625 display: inline-grid;
1643 display: inline-grid;
1626 }
1644 }
1627
1645
1628 .todo-comment-text {
1646 .todo-comment-text {
1629 margin-left: 5px;
1647 margin-left: 5px;
1630 white-space: nowrap;
1648 white-space: nowrap;
1631 overflow: hidden;
1649 overflow: hidden;
1632 text-overflow: ellipsis;
1650 text-overflow: ellipsis;
1633 }
1651 }
1634
1652
1635 table.group_members {
1653 table.group_members {
1636 width: 100%
1654 width: 100%
1637 }
1655 }
1638
1656
1639 .group_members {
1657 .group_members {
1640 margin-top: 0;
1658 margin-top: 0;
1641 padding: 0;
1659 padding: 0;
1642
1660
1643 img {
1661 img {
1644 height: @gravatar-size;
1662 height: @gravatar-size;
1645 width: @gravatar-size;
1663 width: @gravatar-size;
1646 margin-right: .5em;
1664 margin-right: .5em;
1647 margin-left: 3px;
1665 margin-left: 3px;
1648 }
1666 }
1649
1667
1650 .to-delete {
1668 .to-delete {
1651 .user {
1669 .user {
1652 text-decoration: line-through;
1670 text-decoration: line-through;
1653 }
1671 }
1654 }
1672 }
1655 }
1673 }
1656
1674
1657 .compare_view_commits_title {
1675 .compare_view_commits_title {
1658 .disabled {
1676 .disabled {
1659 cursor: inherit;
1677 cursor: inherit;
1660 &:hover{
1678 &:hover{
1661 background-color: inherit;
1679 background-color: inherit;
1662 color: inherit;
1680 color: inherit;
1663 }
1681 }
1664 }
1682 }
1665 }
1683 }
1666
1684
1667 .subtitle-compare {
1685 .subtitle-compare {
1668 margin: -15px 0px 0px 0px;
1686 margin: -15px 0px 0px 0px;
1669 }
1687 }
1670
1688
1671 // new entry in group_members
1689 // new entry in group_members
1672 .td-author-new-entry {
1690 .td-author-new-entry {
1673 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1691 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1674 }
1692 }
1675
1693
1676 .usergroup_member_remove {
1694 .usergroup_member_remove {
1677 width: 16px;
1695 width: 16px;
1678 margin-bottom: 10px;
1696 margin-bottom: 10px;
1679 padding: 0;
1697 padding: 0;
1680 color: black !important;
1698 color: black !important;
1681 cursor: pointer;
1699 cursor: pointer;
1682 }
1700 }
1683
1701
1684 .reviewer_ac .ac-input {
1702 .reviewer_ac .ac-input {
1685 width: 92%;
1703 width: 100%;
1686 margin-bottom: 1em;
1704 margin-bottom: 1em;
1687 }
1705 }
1688
1706
1689 .compare_view_commits tr{
1707 .compare_view_commits tr{
1690 height: 20px;
1708 height: 20px;
1691 }
1709 }
1692 .compare_view_commits td {
1710 .compare_view_commits td {
1693 vertical-align: top;
1711 vertical-align: top;
1694 padding-top: 10px;
1712 padding-top: 10px;
1695 }
1713 }
1696 .compare_view_commits .author {
1714 .compare_view_commits .author {
1697 margin-left: 5px;
1715 margin-left: 5px;
1698 }
1716 }
1699
1717
1700 .compare_view_commits {
1718 .compare_view_commits {
1701 .color-a {
1719 .color-a {
1702 color: @alert1;
1720 color: @alert1;
1703 }
1721 }
1704
1722
1705 .color-c {
1723 .color-c {
1706 color: @color3;
1724 color: @color3;
1707 }
1725 }
1708
1726
1709 .color-r {
1727 .color-r {
1710 color: @color5;
1728 color: @color5;
1711 }
1729 }
1712
1730
1713 .color-a-bg {
1731 .color-a-bg {
1714 background-color: @alert1;
1732 background-color: @alert1;
1715 }
1733 }
1716
1734
1717 .color-c-bg {
1735 .color-c-bg {
1718 background-color: @alert3;
1736 background-color: @alert3;
1719 }
1737 }
1720
1738
1721 .color-r-bg {
1739 .color-r-bg {
1722 background-color: @alert2;
1740 background-color: @alert2;
1723 }
1741 }
1724
1742
1725 .color-a-border {
1743 .color-a-border {
1726 border: 1px solid @alert1;
1744 border: 1px solid @alert1;
1727 }
1745 }
1728
1746
1729 .color-c-border {
1747 .color-c-border {
1730 border: 1px solid @alert3;
1748 border: 1px solid @alert3;
1731 }
1749 }
1732
1750
1733 .color-r-border {
1751 .color-r-border {
1734 border: 1px solid @alert2;
1752 border: 1px solid @alert2;
1735 }
1753 }
1736
1754
1737 .commit-change-indicator {
1755 .commit-change-indicator {
1738 width: 15px;
1756 width: 15px;
1739 height: 15px;
1757 height: 15px;
1740 position: relative;
1758 position: relative;
1741 left: 15px;
1759 left: 15px;
1742 }
1760 }
1743
1761
1744 .commit-change-content {
1762 .commit-change-content {
1745 text-align: center;
1763 text-align: center;
1746 vertical-align: middle;
1764 vertical-align: middle;
1747 line-height: 15px;
1765 line-height: 15px;
1748 }
1766 }
1749 }
1767 }
1750
1768
1751 .compare_view_filepath {
1769 .compare_view_filepath {
1752 color: @grey1;
1770 color: @grey1;
1753 }
1771 }
1754
1772
1755 .show_more {
1773 .show_more {
1756 display: inline-block;
1774 display: inline-block;
1757 width: 0;
1775 width: 0;
1758 height: 0;
1776 height: 0;
1759 vertical-align: middle;
1777 vertical-align: middle;
1760 content: "";
1778 content: "";
1761 border: 4px solid;
1779 border: 4px solid;
1762 border-right-color: transparent;
1780 border-right-color: transparent;
1763 border-bottom-color: transparent;
1781 border-bottom-color: transparent;
1764 border-left-color: transparent;
1782 border-left-color: transparent;
1765 font-size: 0;
1783 font-size: 0;
1766 }
1784 }
1767
1785
1768 .journal_more .show_more {
1786 .journal_more .show_more {
1769 display: inline;
1787 display: inline;
1770
1788
1771 &:after {
1789 &:after {
1772 content: none;
1790 content: none;
1773 }
1791 }
1774 }
1792 }
1775
1793
1776 .compare_view_commits .collapse_commit:after {
1794 .compare_view_commits .collapse_commit:after {
1777 cursor: pointer;
1795 cursor: pointer;
1778 content: "\00A0\25B4";
1796 content: "\00A0\25B4";
1779 margin-left: -3px;
1797 margin-left: -3px;
1780 font-size: 17px;
1798 font-size: 17px;
1781 color: @grey4;
1799 color: @grey4;
1782 }
1800 }
1783
1801
1784 .diff_links {
1802 .diff_links {
1785 margin-left: 8px;
1803 margin-left: 8px;
1786 }
1804 }
1787
1805
1788 #pull_request_overview {
1806 #pull_request_overview {
1789 div.ancestor {
1807 div.ancestor {
1790 margin: -33px 0;
1808 margin: -33px 0;
1791 }
1809 }
1792 }
1810 }
1793
1811
1794 div.ancestor {
1812 div.ancestor {
1795
1813
1796 }
1814 }
1797
1815
1798 .cs_icon_td input[type="checkbox"] {
1816 .cs_icon_td input[type="checkbox"] {
1799 display: none;
1817 display: none;
1800 }
1818 }
1801
1819
1802 .cs_icon_td .expand_file_icon:after {
1820 .cs_icon_td .expand_file_icon:after {
1803 cursor: pointer;
1821 cursor: pointer;
1804 content: "\00A0\25B6";
1822 content: "\00A0\25B6";
1805 font-size: 12px;
1823 font-size: 12px;
1806 color: @grey4;
1824 color: @grey4;
1807 }
1825 }
1808
1826
1809 .cs_icon_td .collapse_file_icon:after {
1827 .cs_icon_td .collapse_file_icon:after {
1810 cursor: pointer;
1828 cursor: pointer;
1811 content: "\00A0\25BC";
1829 content: "\00A0\25BC";
1812 font-size: 12px;
1830 font-size: 12px;
1813 color: @grey4;
1831 color: @grey4;
1814 }
1832 }
1815
1833
1816 /*new binary
1834 /*new binary
1817 NEW_FILENODE = 1
1835 NEW_FILENODE = 1
1818 DEL_FILENODE = 2
1836 DEL_FILENODE = 2
1819 MOD_FILENODE = 3
1837 MOD_FILENODE = 3
1820 RENAMED_FILENODE = 4
1838 RENAMED_FILENODE = 4
1821 COPIED_FILENODE = 5
1839 COPIED_FILENODE = 5
1822 CHMOD_FILENODE = 6
1840 CHMOD_FILENODE = 6
1823 BIN_FILENODE = 7
1841 BIN_FILENODE = 7
1824 */
1842 */
1825 .cs_files_expand {
1843 .cs_files_expand {
1826 font-size: @basefontsize + 5px;
1844 font-size: @basefontsize + 5px;
1827 line-height: 1.8em;
1845 line-height: 1.8em;
1828 float: right;
1846 float: right;
1829 }
1847 }
1830
1848
1831 .cs_files_expand span{
1849 .cs_files_expand span{
1832 color: @rcblue;
1850 color: @rcblue;
1833 cursor: pointer;
1851 cursor: pointer;
1834 }
1852 }
1835 .cs_files {
1853 .cs_files {
1836 clear: both;
1854 clear: both;
1837 padding-bottom: @padding;
1855 padding-bottom: @padding;
1838
1856
1839 .cur_cs {
1857 .cur_cs {
1840 margin: 10px 2px;
1858 margin: 10px 2px;
1841 font-weight: bold;
1859 font-weight: bold;
1842 }
1860 }
1843
1861
1844 .node {
1862 .node {
1845 float: left;
1863 float: left;
1846 }
1864 }
1847
1865
1848 .changes {
1866 .changes {
1849 float: right;
1867 float: right;
1850 color: white;
1868 color: white;
1851 font-size: @basefontsize - 4px;
1869 font-size: @basefontsize - 4px;
1852 margin-top: 4px;
1870 margin-top: 4px;
1853 opacity: 0.6;
1871 opacity: 0.6;
1854 filter: Alpha(opacity=60); /* IE8 and earlier */
1872 filter: Alpha(opacity=60); /* IE8 and earlier */
1855
1873
1856 .added {
1874 .added {
1857 background-color: @alert1;
1875 background-color: @alert1;
1858 float: left;
1876 float: left;
1859 text-align: center;
1877 text-align: center;
1860 }
1878 }
1861
1879
1862 .deleted {
1880 .deleted {
1863 background-color: @alert2;
1881 background-color: @alert2;
1864 float: left;
1882 float: left;
1865 text-align: center;
1883 text-align: center;
1866 }
1884 }
1867
1885
1868 .bin {
1886 .bin {
1869 background-color: @alert1;
1887 background-color: @alert1;
1870 text-align: center;
1888 text-align: center;
1871 }
1889 }
1872
1890
1873 /*new binary*/
1891 /*new binary*/
1874 .bin.bin1 {
1892 .bin.bin1 {
1875 background-color: @alert1;
1893 background-color: @alert1;
1876 text-align: center;
1894 text-align: center;
1877 }
1895 }
1878
1896
1879 /*deleted binary*/
1897 /*deleted binary*/
1880 .bin.bin2 {
1898 .bin.bin2 {
1881 background-color: @alert2;
1899 background-color: @alert2;
1882 text-align: center;
1900 text-align: center;
1883 }
1901 }
1884
1902
1885 /*mod binary*/
1903 /*mod binary*/
1886 .bin.bin3 {
1904 .bin.bin3 {
1887 background-color: @grey2;
1905 background-color: @grey2;
1888 text-align: center;
1906 text-align: center;
1889 }
1907 }
1890
1908
1891 /*rename file*/
1909 /*rename file*/
1892 .bin.bin4 {
1910 .bin.bin4 {
1893 background-color: @alert4;
1911 background-color: @alert4;
1894 text-align: center;
1912 text-align: center;
1895 }
1913 }
1896
1914
1897 /*copied file*/
1915 /*copied file*/
1898 .bin.bin5 {
1916 .bin.bin5 {
1899 background-color: @alert4;
1917 background-color: @alert4;
1900 text-align: center;
1918 text-align: center;
1901 }
1919 }
1902
1920
1903 /*chmod file*/
1921 /*chmod file*/
1904 .bin.bin6 {
1922 .bin.bin6 {
1905 background-color: @grey2;
1923 background-color: @grey2;
1906 text-align: center;
1924 text-align: center;
1907 }
1925 }
1908 }
1926 }
1909 }
1927 }
1910
1928
1911 .cs_files .cs_added, .cs_files .cs_A,
1929 .cs_files .cs_added, .cs_files .cs_A,
1912 .cs_files .cs_added, .cs_files .cs_M,
1930 .cs_files .cs_added, .cs_files .cs_M,
1913 .cs_files .cs_added, .cs_files .cs_D {
1931 .cs_files .cs_added, .cs_files .cs_D {
1914 height: 16px;
1932 height: 16px;
1915 padding-right: 10px;
1933 padding-right: 10px;
1916 margin-top: 7px;
1934 margin-top: 7px;
1917 text-align: left;
1935 text-align: left;
1918 }
1936 }
1919
1937
1920 .cs_icon_td {
1938 .cs_icon_td {
1921 min-width: 16px;
1939 min-width: 16px;
1922 width: 16px;
1940 width: 16px;
1923 }
1941 }
1924
1942
1925 .pull-request-merge {
1943 .pull-request-merge {
1926 border: 1px solid @grey5;
1944 border: 1px solid @grey5;
1927 padding: 10px 0px 20px;
1945 padding: 10px 0px 20px;
1928 margin-top: 10px;
1946 margin-top: 10px;
1929 margin-bottom: 20px;
1947 margin-bottom: 20px;
1930 }
1948 }
1931
1949
1932 .pull-request-merge-refresh {
1950 .pull-request-merge-refresh {
1933 margin: 2px 7px;
1951 margin: 2px 7px;
1934 a {
1952 a {
1935 color: @grey3;
1953 color: @grey3;
1936 }
1954 }
1937 }
1955 }
1938
1956
1939 .pull-request-merge ul {
1957 .pull-request-merge ul {
1940 padding: 0px 0px;
1958 padding: 0px 0px;
1941 }
1959 }
1942
1960
1943 .pull-request-merge li {
1961 .pull-request-merge li {
1944 list-style-type: none;
1962 list-style-type: none;
1945 }
1963 }
1946
1964
1947 .pull-request-merge .pull-request-wrap {
1965 .pull-request-merge .pull-request-wrap {
1948 height: auto;
1966 height: auto;
1949 padding: 0px 0px;
1967 padding: 0px 0px;
1950 text-align: right;
1968 text-align: right;
1951 }
1969 }
1952
1970
1953 .pull-request-merge span {
1971 .pull-request-merge span {
1954 margin-right: 5px;
1972 margin-right: 5px;
1955 }
1973 }
1956
1974
1957 .pull-request-merge-actions {
1975 .pull-request-merge-actions {
1958 min-height: 30px;
1976 min-height: 30px;
1959 padding: 0px 0px;
1977 padding: 0px 0px;
1960 }
1978 }
1961
1979
1962 .pull-request-merge-info {
1980 .pull-request-merge-info {
1963 padding: 0px 5px 5px 0px;
1981 padding: 0px 5px 5px 0px;
1964 }
1982 }
1965
1983
1966 .merge-status {
1984 .merge-status {
1967 margin-right: 5px;
1985 margin-right: 5px;
1968 }
1986 }
1969
1987
1970 .merge-message {
1988 .merge-message {
1971 font-size: 1.2em
1989 font-size: 1.2em
1972 }
1990 }
1973
1991
1974 .merge-message.success i,
1992 .merge-message.success i,
1975 .merge-icon.success i {
1993 .merge-icon.success i {
1976 color:@alert1;
1994 color:@alert1;
1977 }
1995 }
1978
1996
1979 .merge-message.warning i,
1997 .merge-message.warning i,
1980 .merge-icon.warning i {
1998 .merge-icon.warning i {
1981 color: @alert3;
1999 color: @alert3;
1982 }
2000 }
1983
2001
1984 .merge-message.error i,
2002 .merge-message.error i,
1985 .merge-icon.error i {
2003 .merge-icon.error i {
1986 color:@alert2;
2004 color:@alert2;
1987 }
2005 }
1988
2006
1989 .pr-versions {
2007 .pr-versions {
1990 font-size: 1.1em;
2008 font-size: 1.1em;
1991 padding: 7.5px;
2009 padding: 7.5px;
1992
2010
1993 table {
2011 table {
1994
2012
1995 }
2013 }
1996
2014
1997 td {
2015 td {
1998 line-height: 15px;
2016 line-height: 15px;
1999 }
2017 }
2000
2018
2001 .compare-radio-button {
2019 .compare-radio-button {
2002 position: relative;
2020 position: relative;
2003 top: -3px;
2021 top: -3px;
2004 }
2022 }
2005 }
2023 }
2006
2024
2007
2025
2008 #close_pull_request {
2026 #close_pull_request {
2009 margin-right: 0px;
2027 margin-right: 0px;
2010 }
2028 }
2011
2029
2012 .empty_data {
2030 .empty_data {
2013 color: @grey4;
2031 color: @grey4;
2014 }
2032 }
2015
2033
2016 #changeset_compare_view_content {
2034 #changeset_compare_view_content {
2017 clear: both;
2035 clear: both;
2018 width: 100%;
2036 width: 100%;
2019 box-sizing: border-box;
2037 box-sizing: border-box;
2020 .border-radius(@border-radius);
2038 .border-radius(@border-radius);
2021
2039
2022 .help-block {
2040 .help-block {
2023 margin: @padding 0;
2041 margin: @padding 0;
2024 color: @text-color;
2042 color: @text-color;
2025 &.pre-formatting {
2043 &.pre-formatting {
2026 white-space: pre;
2044 white-space: pre;
2027 }
2045 }
2028 }
2046 }
2029
2047
2030 .empty_data {
2048 .empty_data {
2031 margin: @padding 0;
2049 margin: @padding 0;
2032 }
2050 }
2033
2051
2034 .alert {
2052 .alert {
2035 margin-bottom: @space;
2053 margin-bottom: @space;
2036 }
2054 }
2037 }
2055 }
2038
2056
2039 .table_disp {
2057 .table_disp {
2040 .status {
2058 .status {
2041 width: auto;
2059 width: auto;
2042 }
2060 }
2043 }
2061 }
2044
2062
2045
2063
2046 .creation_in_progress {
2064 .creation_in_progress {
2047 color: @grey4
2065 color: @grey4
2048 }
2066 }
2049
2067
2050 .status_box_menu {
2068 .status_box_menu {
2051 margin: 0;
2069 margin: 0;
2052 }
2070 }
2053
2071
2054 .notification-table{
2072 .notification-table{
2055 margin-bottom: @space;
2073 margin-bottom: @space;
2056 display: table;
2074 display: table;
2057 width: 100%;
2075 width: 100%;
2058
2076
2059 .container{
2077 .container{
2060 display: table-row;
2078 display: table-row;
2061
2079
2062 .notification-header{
2080 .notification-header{
2063 border-bottom: @border-thickness solid @border-default-color;
2081 border-bottom: @border-thickness solid @border-default-color;
2064 }
2082 }
2065
2083
2066 .notification-subject{
2084 .notification-subject{
2067 display: table-cell;
2085 display: table-cell;
2068 }
2086 }
2069 }
2087 }
2070 }
2088 }
2071
2089
2072 // Notifications
2090 // Notifications
2073 .notification-header{
2091 .notification-header{
2074 display: table;
2092 display: table;
2075 width: 100%;
2093 width: 100%;
2076 padding: floor(@basefontsize/2) 0;
2094 padding: floor(@basefontsize/2) 0;
2077 line-height: 1em;
2095 line-height: 1em;
2078
2096
2079 .desc, .delete-notifications, .read-notifications{
2097 .desc, .delete-notifications, .read-notifications{
2080 display: table-cell;
2098 display: table-cell;
2081 text-align: left;
2099 text-align: left;
2082 }
2100 }
2083
2101
2084 .delete-notifications, .read-notifications{
2102 .delete-notifications, .read-notifications{
2085 width: 35px;
2103 width: 35px;
2086 min-width: 35px; //fixes when only one button is displayed
2104 min-width: 35px; //fixes when only one button is displayed
2087 }
2105 }
2088 }
2106 }
2089
2107
2090 .notification-body {
2108 .notification-body {
2091 .markdown-block,
2109 .markdown-block,
2092 .rst-block {
2110 .rst-block {
2093 padding: @padding 0;
2111 padding: @padding 0;
2094 }
2112 }
2095
2113
2096 .notification-subject {
2114 .notification-subject {
2097 padding: @textmargin 0;
2115 padding: @textmargin 0;
2098 border-bottom: @border-thickness solid @border-default-color;
2116 border-bottom: @border-thickness solid @border-default-color;
2099 }
2117 }
2100 }
2118 }
2101
2119
2102 .notice-messages {
2120 .notice-messages {
2103 .markdown-block,
2121 .markdown-block,
2104 .rst-block {
2122 .rst-block {
2105 padding: 0;
2123 padding: 0;
2106 }
2124 }
2107 }
2125 }
2108
2126
2109 .notifications_buttons{
2127 .notifications_buttons{
2110 float: right;
2128 float: right;
2111 }
2129 }
2112
2130
2113 #notification-status{
2131 #notification-status{
2114 display: inline;
2132 display: inline;
2115 }
2133 }
2116
2134
2117 // Repositories
2135 // Repositories
2118
2136
2119 #summary.fields{
2137 #summary.fields{
2120 display: table;
2138 display: table;
2121
2139
2122 .field{
2140 .field{
2123 display: table-row;
2141 display: table-row;
2124
2142
2125 .label-summary{
2143 .label-summary{
2126 display: table-cell;
2144 display: table-cell;
2127 min-width: @label-summary-minwidth;
2145 min-width: @label-summary-minwidth;
2128 padding-top: @padding/2;
2146 padding-top: @padding/2;
2129 padding-bottom: @padding/2;
2147 padding-bottom: @padding/2;
2130 padding-right: @padding/2;
2148 padding-right: @padding/2;
2131 }
2149 }
2132
2150
2133 .input{
2151 .input{
2134 display: table-cell;
2152 display: table-cell;
2135 padding: @padding/2;
2153 padding: @padding/2;
2136
2154
2137 input{
2155 input{
2138 min-width: 29em;
2156 min-width: 29em;
2139 padding: @padding/4;
2157 padding: @padding/4;
2140 }
2158 }
2141 }
2159 }
2142 .statistics, .downloads{
2160 .statistics, .downloads{
2143 .disabled{
2161 .disabled{
2144 color: @grey4;
2162 color: @grey4;
2145 }
2163 }
2146 }
2164 }
2147 }
2165 }
2148 }
2166 }
2149
2167
2150 #summary{
2168 #summary{
2151 width: 70%;
2169 width: 70%;
2152 }
2170 }
2153
2171
2154
2172
2155 // Journal
2173 // Journal
2156 .journal.title {
2174 .journal.title {
2157 h5 {
2175 h5 {
2158 float: left;
2176 float: left;
2159 margin: 0;
2177 margin: 0;
2160 width: 70%;
2178 width: 70%;
2161 }
2179 }
2162
2180
2163 ul {
2181 ul {
2164 float: right;
2182 float: right;
2165 display: inline-block;
2183 display: inline-block;
2166 margin: 0;
2184 margin: 0;
2167 width: 30%;
2185 width: 30%;
2168 text-align: right;
2186 text-align: right;
2169
2187
2170 li {
2188 li {
2171 display: inline;
2189 display: inline;
2172 font-size: @journal-fontsize;
2190 font-size: @journal-fontsize;
2173 line-height: 1em;
2191 line-height: 1em;
2174
2192
2175 list-style-type: none;
2193 list-style-type: none;
2176 }
2194 }
2177 }
2195 }
2178 }
2196 }
2179
2197
2180 .filterexample {
2198 .filterexample {
2181 position: absolute;
2199 position: absolute;
2182 top: 95px;
2200 top: 95px;
2183 left: @contentpadding;
2201 left: @contentpadding;
2184 color: @rcblue;
2202 color: @rcblue;
2185 font-size: 11px;
2203 font-size: 11px;
2186 font-family: @text-regular;
2204 font-family: @text-regular;
2187 cursor: help;
2205 cursor: help;
2188
2206
2189 &:hover {
2207 &:hover {
2190 color: @rcdarkblue;
2208 color: @rcdarkblue;
2191 }
2209 }
2192
2210
2193 @media (max-width:768px) {
2211 @media (max-width:768px) {
2194 position: relative;
2212 position: relative;
2195 top: auto;
2213 top: auto;
2196 left: auto;
2214 left: auto;
2197 display: block;
2215 display: block;
2198 }
2216 }
2199 }
2217 }
2200
2218
2201
2219
2202 #journal{
2220 #journal{
2203 margin-bottom: @space;
2221 margin-bottom: @space;
2204
2222
2205 .journal_day{
2223 .journal_day{
2206 margin-bottom: @textmargin/2;
2224 margin-bottom: @textmargin/2;
2207 padding-bottom: @textmargin/2;
2225 padding-bottom: @textmargin/2;
2208 font-size: @journal-fontsize;
2226 font-size: @journal-fontsize;
2209 border-bottom: @border-thickness solid @border-default-color;
2227 border-bottom: @border-thickness solid @border-default-color;
2210 }
2228 }
2211
2229
2212 .journal_container{
2230 .journal_container{
2213 margin-bottom: @space;
2231 margin-bottom: @space;
2214
2232
2215 .journal_user{
2233 .journal_user{
2216 display: inline-block;
2234 display: inline-block;
2217 }
2235 }
2218 .journal_action_container{
2236 .journal_action_container{
2219 display: block;
2237 display: block;
2220 margin-top: @textmargin;
2238 margin-top: @textmargin;
2221
2239
2222 div{
2240 div{
2223 display: inline;
2241 display: inline;
2224 }
2242 }
2225
2243
2226 div.journal_action_params{
2244 div.journal_action_params{
2227 display: block;
2245 display: block;
2228 }
2246 }
2229
2247
2230 div.journal_repo:after{
2248 div.journal_repo:after{
2231 content: "\A";
2249 content: "\A";
2232 white-space: pre;
2250 white-space: pre;
2233 }
2251 }
2234
2252
2235 div.date{
2253 div.date{
2236 display: block;
2254 display: block;
2237 margin-bottom: @textmargin;
2255 margin-bottom: @textmargin;
2238 }
2256 }
2239 }
2257 }
2240 }
2258 }
2241 }
2259 }
2242
2260
2243 // Files
2261 // Files
2244 .edit-file-title {
2262 .edit-file-title {
2245 font-size: 16px;
2263 font-size: 16px;
2246
2264
2247 .title-heading {
2265 .title-heading {
2248 padding: 2px;
2266 padding: 2px;
2249 }
2267 }
2250 }
2268 }
2251
2269
2252 .edit-file-fieldset {
2270 .edit-file-fieldset {
2253 margin: @sidebarpadding 0;
2271 margin: @sidebarpadding 0;
2254
2272
2255 .fieldset {
2273 .fieldset {
2256 .left-label {
2274 .left-label {
2257 width: 13%;
2275 width: 13%;
2258 }
2276 }
2259 .right-content {
2277 .right-content {
2260 width: 87%;
2278 width: 87%;
2261 max-width: 100%;
2279 max-width: 100%;
2262 }
2280 }
2263 .filename-label {
2281 .filename-label {
2264 margin-top: 13px;
2282 margin-top: 13px;
2265 }
2283 }
2266 .commit-message-label {
2284 .commit-message-label {
2267 margin-top: 4px;
2285 margin-top: 4px;
2268 }
2286 }
2269 .file-upload-input {
2287 .file-upload-input {
2270 input {
2288 input {
2271 display: none;
2289 display: none;
2272 }
2290 }
2273 margin-top: 10px;
2291 margin-top: 10px;
2274 }
2292 }
2275 .file-upload-label {
2293 .file-upload-label {
2276 margin-top: 10px;
2294 margin-top: 10px;
2277 }
2295 }
2278 p {
2296 p {
2279 margin-top: 5px;
2297 margin-top: 5px;
2280 }
2298 }
2281
2299
2282 }
2300 }
2283 .custom-path-link {
2301 .custom-path-link {
2284 margin-left: 5px;
2302 margin-left: 5px;
2285 }
2303 }
2286 #commit {
2304 #commit {
2287 resize: vertical;
2305 resize: vertical;
2288 }
2306 }
2289 }
2307 }
2290
2308
2291 .delete-file-preview {
2309 .delete-file-preview {
2292 max-height: 250px;
2310 max-height: 250px;
2293 }
2311 }
2294
2312
2295 .new-file,
2313 .new-file,
2296 #filter_activate,
2314 #filter_activate,
2297 #filter_deactivate {
2315 #filter_deactivate {
2298 float: right;
2316 float: right;
2299 margin: 0 0 0 10px;
2317 margin: 0 0 0 10px;
2300 }
2318 }
2301
2319
2302 .file-upload-transaction-wrapper {
2320 .file-upload-transaction-wrapper {
2303 margin-top: 57px;
2321 margin-top: 57px;
2304 clear: both;
2322 clear: both;
2305 }
2323 }
2306
2324
2307 .file-upload-transaction-wrapper .error {
2325 .file-upload-transaction-wrapper .error {
2308 color: @color5;
2326 color: @color5;
2309 }
2327 }
2310
2328
2311 .file-upload-transaction {
2329 .file-upload-transaction {
2312 min-height: 200px;
2330 min-height: 200px;
2313 padding: 54px;
2331 padding: 54px;
2314 border: 1px solid @grey5;
2332 border: 1px solid @grey5;
2315 text-align: center;
2333 text-align: center;
2316 clear: both;
2334 clear: both;
2317 }
2335 }
2318
2336
2319 .file-upload-transaction i {
2337 .file-upload-transaction i {
2320 font-size: 48px
2338 font-size: 48px
2321 }
2339 }
2322
2340
2323 h3.files_location{
2341 h3.files_location{
2324 line-height: 2.4em;
2342 line-height: 2.4em;
2325 }
2343 }
2326
2344
2327 .browser-nav {
2345 .browser-nav {
2328 width: 100%;
2346 width: 100%;
2329 display: table;
2347 display: table;
2330 margin-bottom: 20px;
2348 margin-bottom: 20px;
2331
2349
2332 .info_box {
2350 .info_box {
2333 float: left;
2351 float: left;
2334 display: inline-table;
2352 display: inline-table;
2335 height: 2.5em;
2353 height: 2.5em;
2336
2354
2337 .browser-cur-rev, .info_box_elem {
2355 .browser-cur-rev, .info_box_elem {
2338 display: table-cell;
2356 display: table-cell;
2339 vertical-align: middle;
2357 vertical-align: middle;
2340 }
2358 }
2341
2359
2342 .drop-menu {
2360 .drop-menu {
2343 margin: 0 10px;
2361 margin: 0 10px;
2344 }
2362 }
2345
2363
2346 .info_box_elem {
2364 .info_box_elem {
2347 border-top: @border-thickness solid @grey5;
2365 border-top: @border-thickness solid @grey5;
2348 border-bottom: @border-thickness solid @grey5;
2366 border-bottom: @border-thickness solid @grey5;
2349 box-shadow: @button-shadow;
2367 box-shadow: @button-shadow;
2350
2368
2351 #at_rev, a {
2369 #at_rev, a {
2352 padding: 0.6em 0.4em;
2370 padding: 0.6em 0.4em;
2353 margin: 0;
2371 margin: 0;
2354 .box-shadow(none);
2372 .box-shadow(none);
2355 border: 0;
2373 border: 0;
2356 height: 12px;
2374 height: 12px;
2357 color: @grey2;
2375 color: @grey2;
2358 }
2376 }
2359
2377
2360 input#at_rev {
2378 input#at_rev {
2361 max-width: 50px;
2379 max-width: 50px;
2362 text-align: center;
2380 text-align: center;
2363 }
2381 }
2364
2382
2365 &.previous {
2383 &.previous {
2366 border: @border-thickness solid @grey5;
2384 border: @border-thickness solid @grey5;
2367 border-top-left-radius: @border-radius;
2385 border-top-left-radius: @border-radius;
2368 border-bottom-left-radius: @border-radius;
2386 border-bottom-left-radius: @border-radius;
2369
2387
2370 &:hover {
2388 &:hover {
2371 border-color: @grey4;
2389 border-color: @grey4;
2372 }
2390 }
2373
2391
2374 .disabled {
2392 .disabled {
2375 color: @grey5;
2393 color: @grey5;
2376 cursor: not-allowed;
2394 cursor: not-allowed;
2377 opacity: 0.5;
2395 opacity: 0.5;
2378 }
2396 }
2379 }
2397 }
2380
2398
2381 &.next {
2399 &.next {
2382 border: @border-thickness solid @grey5;
2400 border: @border-thickness solid @grey5;
2383 border-top-right-radius: @border-radius;
2401 border-top-right-radius: @border-radius;
2384 border-bottom-right-radius: @border-radius;
2402 border-bottom-right-radius: @border-radius;
2385
2403
2386 &:hover {
2404 &:hover {
2387 border-color: @grey4;
2405 border-color: @grey4;
2388 }
2406 }
2389
2407
2390 .disabled {
2408 .disabled {
2391 color: @grey5;
2409 color: @grey5;
2392 cursor: not-allowed;
2410 cursor: not-allowed;
2393 opacity: 0.5;
2411 opacity: 0.5;
2394 }
2412 }
2395 }
2413 }
2396 }
2414 }
2397
2415
2398 .browser-cur-rev {
2416 .browser-cur-rev {
2399
2417
2400 span{
2418 span{
2401 margin: 0;
2419 margin: 0;
2402 color: @rcblue;
2420 color: @rcblue;
2403 height: 12px;
2421 height: 12px;
2404 display: inline-block;
2422 display: inline-block;
2405 padding: 0.7em 1em ;
2423 padding: 0.7em 1em ;
2406 border: @border-thickness solid @rcblue;
2424 border: @border-thickness solid @rcblue;
2407 margin-right: @padding;
2425 margin-right: @padding;
2408 }
2426 }
2409 }
2427 }
2410
2428
2411 }
2429 }
2412
2430
2413 .select-index-number {
2431 .select-index-number {
2414 margin: 0 0 0 20px;
2432 margin: 0 0 0 20px;
2415 color: @grey3;
2433 color: @grey3;
2416 }
2434 }
2417
2435
2418 .search_activate {
2436 .search_activate {
2419 display: table-cell;
2437 display: table-cell;
2420 vertical-align: middle;
2438 vertical-align: middle;
2421
2439
2422 input, label{
2440 input, label{
2423 margin: 0;
2441 margin: 0;
2424 padding: 0;
2442 padding: 0;
2425 }
2443 }
2426
2444
2427 input{
2445 input{
2428 margin-left: @textmargin;
2446 margin-left: @textmargin;
2429 }
2447 }
2430
2448
2431 }
2449 }
2432 }
2450 }
2433
2451
2434 .browser-cur-rev{
2452 .browser-cur-rev{
2435 margin-bottom: @textmargin;
2453 margin-bottom: @textmargin;
2436 }
2454 }
2437
2455
2438 #node_filter_box_loading{
2456 #node_filter_box_loading{
2439 .info_text;
2457 .info_text;
2440 }
2458 }
2441
2459
2442 .browser-search {
2460 .browser-search {
2443 margin: -25px 0px 5px 0px;
2461 margin: -25px 0px 5px 0px;
2444 }
2462 }
2445
2463
2446 .files-quick-filter {
2464 .files-quick-filter {
2447 float: right;
2465 float: right;
2448 width: 180px;
2466 width: 180px;
2449 position: relative;
2467 position: relative;
2450 }
2468 }
2451
2469
2452 .files-filter-box {
2470 .files-filter-box {
2453 display: flex;
2471 display: flex;
2454 padding: 0px;
2472 padding: 0px;
2455 border-radius: 3px;
2473 border-radius: 3px;
2456 margin-bottom: 0;
2474 margin-bottom: 0;
2457
2475
2458 a {
2476 a {
2459 border: none !important;
2477 border: none !important;
2460 }
2478 }
2461
2479
2462 li {
2480 li {
2463 list-style-type: none
2481 list-style-type: none
2464 }
2482 }
2465 }
2483 }
2466
2484
2467 .files-filter-box-path {
2485 .files-filter-box-path {
2468 line-height: 33px;
2486 line-height: 33px;
2469 padding: 0;
2487 padding: 0;
2470 width: 20px;
2488 width: 20px;
2471 position: absolute;
2489 position: absolute;
2472 z-index: 11;
2490 z-index: 11;
2473 left: 5px;
2491 left: 5px;
2474 }
2492 }
2475
2493
2476 .files-filter-box-input {
2494 .files-filter-box-input {
2477 margin-right: 0;
2495 margin-right: 0;
2478
2496
2479 input {
2497 input {
2480 border: 1px solid @white;
2498 border: 1px solid @white;
2481 padding-left: 25px;
2499 padding-left: 25px;
2482 width: 145px;
2500 width: 145px;
2483
2501
2484 &:hover {
2502 &:hover {
2485 border-color: @grey6;
2503 border-color: @grey6;
2486 }
2504 }
2487
2505
2488 &:focus {
2506 &:focus {
2489 border-color: @grey5;
2507 border-color: @grey5;
2490 }
2508 }
2491 }
2509 }
2492 }
2510 }
2493
2511
2494 .browser-result{
2512 .browser-result{
2495 td a{
2513 td a{
2496 margin-left: 0.5em;
2514 margin-left: 0.5em;
2497 display: inline-block;
2515 display: inline-block;
2498
2516
2499 em {
2517 em {
2500 font-weight: @text-bold-weight;
2518 font-weight: @text-bold-weight;
2501 font-family: @text-bold;
2519 font-family: @text-bold;
2502 }
2520 }
2503 }
2521 }
2504 }
2522 }
2505
2523
2506 .browser-highlight{
2524 .browser-highlight{
2507 background-color: @grey5-alpha;
2525 background-color: @grey5-alpha;
2508 }
2526 }
2509
2527
2510
2528
2511 .edit-file-fieldset #location,
2529 .edit-file-fieldset #location,
2512 .edit-file-fieldset #filename {
2530 .edit-file-fieldset #filename {
2513 display: flex;
2531 display: flex;
2514 width: -moz-available; /* WebKit-based browsers will ignore this. */
2532 width: -moz-available; /* WebKit-based browsers will ignore this. */
2515 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2533 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2516 width: fill-available;
2534 width: fill-available;
2517 border: 0;
2535 border: 0;
2518 }
2536 }
2519
2537
2520 .path-items {
2538 .path-items {
2521 display: flex;
2539 display: flex;
2522 padding: 0;
2540 padding: 0;
2523 border: 1px solid #eeeeee;
2541 border: 1px solid #eeeeee;
2524 width: 100%;
2542 width: 100%;
2525 float: left;
2543 float: left;
2526
2544
2527 .breadcrumb-path {
2545 .breadcrumb-path {
2528 line-height: 30px;
2546 line-height: 30px;
2529 padding: 0 4px;
2547 padding: 0 4px;
2530 white-space: nowrap;
2548 white-space: nowrap;
2531 }
2549 }
2532
2550
2533 .upload-form {
2551 .upload-form {
2534 margin-top: 46px;
2552 margin-top: 46px;
2535 }
2553 }
2536
2554
2537 .location-path {
2555 .location-path {
2538 width: -moz-available; /* WebKit-based browsers will ignore this. */
2556 width: -moz-available; /* WebKit-based browsers will ignore this. */
2539 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2557 width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
2540 width: fill-available;
2558 width: fill-available;
2541
2559
2542 .file-name-input {
2560 .file-name-input {
2543 padding: 0.5em 0;
2561 padding: 0.5em 0;
2544 }
2562 }
2545
2563
2546 }
2564 }
2547
2565
2548 ul {
2566 ul {
2549 display: flex;
2567 display: flex;
2550 margin: 0;
2568 margin: 0;
2551 padding: 0;
2569 padding: 0;
2552 width: 100%;
2570 width: 100%;
2553 }
2571 }
2554
2572
2555 li {
2573 li {
2556 list-style-type: none;
2574 list-style-type: none;
2557 }
2575 }
2558
2576
2559 }
2577 }
2560
2578
2561 .editor-items {
2579 .editor-items {
2562 height: 40px;
2580 height: 40px;
2563 margin: 10px 0 -17px 10px;
2581 margin: 10px 0 -17px 10px;
2564
2582
2565 .editor-action {
2583 .editor-action {
2566 cursor: pointer;
2584 cursor: pointer;
2567 }
2585 }
2568
2586
2569 .editor-action.active {
2587 .editor-action.active {
2570 border-bottom: 2px solid #5C5C5C;
2588 border-bottom: 2px solid #5C5C5C;
2571 }
2589 }
2572
2590
2573 li {
2591 li {
2574 list-style-type: none;
2592 list-style-type: none;
2575 }
2593 }
2576 }
2594 }
2577
2595
2578 .edit-file-fieldset .message textarea {
2596 .edit-file-fieldset .message textarea {
2579 border: 1px solid #eeeeee;
2597 border: 1px solid #eeeeee;
2580 }
2598 }
2581
2599
2582 #files_data .codeblock {
2600 #files_data .codeblock {
2583 background-color: #F5F5F5;
2601 background-color: #F5F5F5;
2584 }
2602 }
2585
2603
2586 #editor_preview {
2604 #editor_preview {
2587 background: white;
2605 background: white;
2588 }
2606 }
2589
2607
2590 .show-editor {
2608 .show-editor {
2591 padding: 10px;
2609 padding: 10px;
2592 background-color: white;
2610 background-color: white;
2593
2611
2594 }
2612 }
2595
2613
2596 .show-preview {
2614 .show-preview {
2597 padding: 10px;
2615 padding: 10px;
2598 background-color: white;
2616 background-color: white;
2599 border-left: 1px solid #eeeeee;
2617 border-left: 1px solid #eeeeee;
2600 }
2618 }
2601 // quick filter
2619 // quick filter
2602 .grid-quick-filter {
2620 .grid-quick-filter {
2603 float: right;
2621 float: right;
2604 position: relative;
2622 position: relative;
2605 }
2623 }
2606
2624
2607 .grid-filter-box {
2625 .grid-filter-box {
2608 display: flex;
2626 display: flex;
2609 padding: 0px;
2627 padding: 0px;
2610 border-radius: 3px;
2628 border-radius: 3px;
2611 margin-bottom: 0;
2629 margin-bottom: 0;
2612
2630
2613 a {
2631 a {
2614 border: none !important;
2632 border: none !important;
2615 }
2633 }
2616
2634
2617 li {
2635 li {
2618 list-style-type: none
2636 list-style-type: none
2619 }
2637 }
2620 }
2638 }
2621
2639
2622 .grid-filter-box-icon {
2640 .grid-filter-box-icon {
2623 line-height: 33px;
2641 line-height: 33px;
2624 padding: 0;
2642 padding: 0;
2625 width: 20px;
2643 width: 20px;
2626 position: absolute;
2644 position: absolute;
2627 z-index: 11;
2645 z-index: 11;
2628 left: 5px;
2646 left: 5px;
2629 }
2647 }
2630
2648
2631 .grid-filter-box-input {
2649 .grid-filter-box-input {
2632 margin-right: 0;
2650 margin-right: 0;
2633
2651
2634 input {
2652 input {
2635 border: 1px solid @white;
2653 border: 1px solid @white;
2636 padding-left: 25px;
2654 padding-left: 25px;
2637 width: 145px;
2655 width: 145px;
2638
2656
2639 &:hover {
2657 &:hover {
2640 border-color: @grey6;
2658 border-color: @grey6;
2641 }
2659 }
2642
2660
2643 &:focus {
2661 &:focus {
2644 border-color: @grey5;
2662 border-color: @grey5;
2645 }
2663 }
2646 }
2664 }
2647 }
2665 }
2648
2666
2649
2667
2650
2668
2651 // Search
2669 // Search
2652
2670
2653 .search-form{
2671 .search-form{
2654 #q {
2672 #q {
2655 width: @search-form-width;
2673 width: @search-form-width;
2656 }
2674 }
2657 .fields{
2675 .fields{
2658 margin: 0 0 @space;
2676 margin: 0 0 @space;
2659 }
2677 }
2660
2678
2661 label{
2679 label{
2662 display: inline-block;
2680 display: inline-block;
2663 margin-right: @textmargin;
2681 margin-right: @textmargin;
2664 padding-top: 0.25em;
2682 padding-top: 0.25em;
2665 }
2683 }
2666
2684
2667
2685
2668 .results{
2686 .results{
2669 clear: both;
2687 clear: both;
2670 margin: 0 0 @padding;
2688 margin: 0 0 @padding;
2671 }
2689 }
2672
2690
2673 .search-tags {
2691 .search-tags {
2674 padding: 5px 0;
2692 padding: 5px 0;
2675 }
2693 }
2676 }
2694 }
2677
2695
2678 div.search-feedback-items {
2696 div.search-feedback-items {
2679 display: inline-block;
2697 display: inline-block;
2680 }
2698 }
2681
2699
2682 div.search-code-body {
2700 div.search-code-body {
2683 background-color: #ffffff; padding: 5px 0 5px 10px;
2701 background-color: #ffffff; padding: 5px 0 5px 10px;
2684 pre {
2702 pre {
2685 .match { background-color: #faffa6;}
2703 .match { background-color: #faffa6;}
2686 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2704 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2687 }
2705 }
2688 }
2706 }
2689
2707
2690 .expand_commit.search {
2708 .expand_commit.search {
2691 .show_more.open {
2709 .show_more.open {
2692 height: auto;
2710 height: auto;
2693 max-height: none;
2711 max-height: none;
2694 }
2712 }
2695 }
2713 }
2696
2714
2697 .search-results {
2715 .search-results {
2698
2716
2699 h2 {
2717 h2 {
2700 margin-bottom: 0;
2718 margin-bottom: 0;
2701 }
2719 }
2702 .codeblock {
2720 .codeblock {
2703 border: none;
2721 border: none;
2704 background: transparent;
2722 background: transparent;
2705 }
2723 }
2706
2724
2707 .codeblock-header {
2725 .codeblock-header {
2708 border: none;
2726 border: none;
2709 background: transparent;
2727 background: transparent;
2710 }
2728 }
2711
2729
2712 .code-body {
2730 .code-body {
2713 border: @border-thickness solid @grey6;
2731 border: @border-thickness solid @grey6;
2714 .border-radius(@border-radius);
2732 .border-radius(@border-radius);
2715 }
2733 }
2716
2734
2717 .td-commit {
2735 .td-commit {
2718 &:extend(pre);
2736 &:extend(pre);
2719 border-bottom: @border-thickness solid @border-default-color;
2737 border-bottom: @border-thickness solid @border-default-color;
2720 }
2738 }
2721
2739
2722 .message {
2740 .message {
2723 height: auto;
2741 height: auto;
2724 max-width: 350px;
2742 max-width: 350px;
2725 white-space: normal;
2743 white-space: normal;
2726 text-overflow: initial;
2744 text-overflow: initial;
2727 overflow: visible;
2745 overflow: visible;
2728
2746
2729 .match { background-color: #faffa6;}
2747 .match { background-color: #faffa6;}
2730 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2748 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2731 }
2749 }
2732
2750
2733 .path {
2751 .path {
2734 border-bottom: none !important;
2752 border-bottom: none !important;
2735 border-left: 1px solid @grey6 !important;
2753 border-left: 1px solid @grey6 !important;
2736 border-right: 1px solid @grey6 !important;
2754 border-right: 1px solid @grey6 !important;
2737 }
2755 }
2738 }
2756 }
2739
2757
2740 table.rctable td.td-search-results div {
2758 table.rctable td.td-search-results div {
2741 max-width: 100%;
2759 max-width: 100%;
2742 }
2760 }
2743
2761
2744 #tip-box, .tip-box{
2762 #tip-box, .tip-box{
2745 padding: @menupadding/2;
2763 padding: @menupadding/2;
2746 display: block;
2764 display: block;
2747 border: @border-thickness solid @border-highlight-color;
2765 border: @border-thickness solid @border-highlight-color;
2748 .border-radius(@border-radius);
2766 .border-radius(@border-radius);
2749 background-color: white;
2767 background-color: white;
2750 z-index: 99;
2768 z-index: 99;
2751 white-space: pre-wrap;
2769 white-space: pre-wrap;
2752 }
2770 }
2753
2771
2754 #linktt {
2772 #linktt {
2755 width: 79px;
2773 width: 79px;
2756 }
2774 }
2757
2775
2758 #help_kb .modal-content{
2776 #help_kb .modal-content{
2759 max-width: 750px;
2777 max-width: 800px;
2760 margin: 10% auto;
2778 margin: 10% auto;
2761
2779
2762 table{
2780 table{
2763 td,th{
2781 td,th{
2764 border-bottom: none;
2782 border-bottom: none;
2765 line-height: 2.5em;
2783 line-height: 2.5em;
2766 }
2784 }
2767 th{
2785 th{
2768 padding-bottom: @textmargin/2;
2786 padding-bottom: @textmargin/2;
2769 }
2787 }
2770 td.keys{
2788 td.keys{
2771 text-align: center;
2789 text-align: center;
2772 }
2790 }
2773 }
2791 }
2774
2792
2775 .block-left{
2793 .block-left{
2776 width: 45%;
2794 width: 45%;
2777 margin-right: 5%;
2795 margin-right: 5%;
2778 }
2796 }
2779 .modal-footer{
2797 .modal-footer{
2780 clear: both;
2798 clear: both;
2781 }
2799 }
2782 .key.tag{
2800 .key.tag{
2783 padding: 0.5em;
2801 padding: 0.5em;
2784 background-color: @rcblue;
2802 background-color: @rcblue;
2785 color: white;
2803 color: white;
2786 border-color: @rcblue;
2804 border-color: @rcblue;
2787 .box-shadow(none);
2805 .box-shadow(none);
2788 }
2806 }
2789 }
2807 }
2790
2808
2791
2809
2792
2810
2793 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2811 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2794
2812
2795 @import 'statistics-graph';
2813 @import 'statistics-graph';
2796 @import 'tables';
2814 @import 'tables';
2797 @import 'forms';
2815 @import 'forms';
2798 @import 'diff';
2816 @import 'diff';
2799 @import 'summary';
2817 @import 'summary';
2800 @import 'navigation';
2818 @import 'navigation';
2801
2819
2802 //--- SHOW/HIDE SECTIONS --//
2820 //--- SHOW/HIDE SECTIONS --//
2803
2821
2804 .btn-collapse {
2822 .btn-collapse {
2805 float: right;
2823 float: right;
2806 text-align: right;
2824 text-align: right;
2807 font-family: @text-light;
2825 font-family: @text-light;
2808 font-size: @basefontsize;
2826 font-size: @basefontsize;
2809 cursor: pointer;
2827 cursor: pointer;
2810 border: none;
2828 border: none;
2811 color: @rcblue;
2829 color: @rcblue;
2812 }
2830 }
2813
2831
2814 table.rctable,
2832 table.rctable,
2815 table.dataTable {
2833 table.dataTable {
2816 .btn-collapse {
2834 .btn-collapse {
2817 float: right;
2835 float: right;
2818 text-align: right;
2836 text-align: right;
2819 }
2837 }
2820 }
2838 }
2821
2839
2822 table.rctable {
2840 table.rctable {
2823 &.permissions {
2841 &.permissions {
2824
2842
2825 th.td-owner {
2843 th.td-owner {
2826 padding: 0;
2844 padding: 0;
2827 }
2845 }
2828
2846
2829 th {
2847 th {
2830 font-weight: normal;
2848 font-weight: normal;
2831 padding: 0 5px;
2849 padding: 0 5px;
2832 }
2850 }
2833
2851
2834 }
2852 }
2835 }
2853 }
2836
2854
2837
2855
2838 // TODO: johbo: Fix for IE10, this avoids that we see a border
2856 // TODO: johbo: Fix for IE10, this avoids that we see a border
2839 // and padding around checkboxes and radio boxes. Move to the right place,
2857 // and padding around checkboxes and radio boxes. Move to the right place,
2840 // or better: Remove this once we did the form refactoring.
2858 // or better: Remove this once we did the form refactoring.
2841 input[type=checkbox],
2859 input[type=checkbox],
2842 input[type=radio] {
2860 input[type=radio] {
2843 padding: 0;
2861 padding: 0;
2844 border: none;
2862 border: none;
2845 }
2863 }
2846
2864
2847 .toggle-ajax-spinner{
2865 .toggle-ajax-spinner{
2848 height: 16px;
2866 height: 16px;
2849 width: 16px;
2867 width: 16px;
2850 }
2868 }
2851
2869
2852
2870
2853 .markup-form .clearfix {
2871 .markup-form .clearfix {
2854 .border-radius(@border-radius);
2872 .border-radius(@border-radius);
2855 margin: 0px;
2873 margin: 0px;
2856 }
2874 }
2857
2875
2858 .markup-form-area {
2876 .markup-form-area {
2859 padding: 8px 12px;
2877 padding: 8px 12px;
2860 border: 1px solid @grey4;
2878 border: 1px solid @grey4;
2861 .border-radius(@border-radius);
2879 .border-radius(@border-radius);
2862 }
2880 }
2863
2881
2864 .markup-form-area-header .nav-links {
2882 .markup-form-area-header .nav-links {
2865 display: flex;
2883 display: flex;
2866 flex-flow: row wrap;
2884 flex-flow: row wrap;
2867 -webkit-flex-flow: row wrap;
2885 -webkit-flex-flow: row wrap;
2868 width: 100%;
2886 width: 100%;
2869 }
2887 }
2870
2888
2871 .markup-form-area-footer {
2889 .markup-form-area-footer {
2872 display: flex;
2890 display: flex;
2873 }
2891 }
2874
2892
2875 .markup-form-area-footer .toolbar {
2893 .markup-form-area-footer .toolbar {
2876
2894
2877 }
2895 }
2878
2896
2879 // markup Form
2897 // markup Form
2880 div.markup-form {
2898 div.markup-form {
2881 margin-top: 20px;
2899 margin-top: 20px;
2882 }
2900 }
2883
2901
2884 .markup-form strong {
2902 .markup-form strong {
2885 display: block;
2903 display: block;
2886 margin-bottom: 15px;
2904 margin-bottom: 15px;
2887 }
2905 }
2888
2906
2889 .markup-form textarea {
2907 .markup-form textarea {
2890 width: 100%;
2908 width: 100%;
2891 height: 100px;
2909 height: 100px;
2892 font-family: @text-monospace;
2910 font-family: @text-monospace;
2893 }
2911 }
2894
2912
2895 form.markup-form {
2913 form.markup-form {
2896 margin-top: 10px;
2914 margin-top: 10px;
2897 margin-left: 10px;
2915 margin-left: 10px;
2898 }
2916 }
2899
2917
2900 .markup-form .comment-block-ta,
2918 .markup-form .comment-block-ta,
2901 .markup-form .preview-box {
2919 .markup-form .preview-box {
2902 .border-radius(@border-radius);
2920 .border-radius(@border-radius);
2903 .box-sizing(border-box);
2921 .box-sizing(border-box);
2904 background-color: white;
2922 background-color: white;
2905 }
2923 }
2906
2924
2907 .markup-form .preview-box.unloaded {
2925 .markup-form .preview-box.unloaded {
2908 height: 50px;
2926 height: 50px;
2909 text-align: center;
2927 text-align: center;
2910 padding: 20px;
2928 padding: 20px;
2911 background-color: white;
2929 background-color: white;
2912 }
2930 }
2913
2931
2914
2932
2915 .dropzone-wrapper {
2933 .dropzone-wrapper {
2916 border: 1px solid @grey5;
2934 border: 1px solid @grey5;
2917 padding: 20px;
2935 padding: 20px;
2918 }
2936 }
2919
2937
2920 .dropzone,
2938 .dropzone,
2921 .dropzone-pure {
2939 .dropzone-pure {
2922 border: 2px dashed @grey5;
2940 border: 2px dashed @grey5;
2923 border-radius: 5px;
2941 border-radius: 5px;
2924 background: white;
2942 background: white;
2925 min-height: 200px;
2943 min-height: 200px;
2926 padding: 54px;
2944 padding: 54px;
2927
2945
2928 .dz-message {
2946 .dz-message {
2929 font-weight: 700;
2947 font-weight: 700;
2930 text-align: center;
2948 text-align: center;
2931 margin: 2em 0;
2949 margin: 2em 0;
2932 }
2950 }
2933
2951
2934 }
2952 }
2935
2953
2936 .dz-preview {
2954 .dz-preview {
2937 margin: 10px 0 !important;
2955 margin: 10px 0 !important;
2938 position: relative;
2956 position: relative;
2939 vertical-align: top;
2957 vertical-align: top;
2940 padding: 10px;
2958 padding: 10px;
2941 border-bottom: 1px solid @grey5;
2959 border-bottom: 1px solid @grey5;
2942 }
2960 }
2943
2961
2944 .dz-filename {
2962 .dz-filename {
2945 font-weight: 700;
2963 font-weight: 700;
2946 float: left;
2964 float: left;
2947 }
2965 }
2948
2966
2949 .dz-sending {
2967 .dz-sending {
2950 float: right;
2968 float: right;
2951 }
2969 }
2952
2970
2953 .dz-response {
2971 .dz-response {
2954 clear: both
2972 clear: both
2955 }
2973 }
2956
2974
2957 .dz-filename-size {
2975 .dz-filename-size {
2958 float: right
2976 float: right
2959 }
2977 }
2960
2978
2961 .dz-error-message {
2979 .dz-error-message {
2962 color: @alert2;
2980 color: @alert2;
2963 padding-top: 10px;
2981 padding-top: 10px;
2964 clear: both;
2982 clear: both;
2965 }
2983 }
2966
2984
2967
2985
2968 .user-hovercard {
2986 .user-hovercard {
2969 padding: 5px;
2987 padding: 5px;
2970 }
2988 }
2971
2989
2972 .user-hovercard-icon {
2990 .user-hovercard-icon {
2973 display: inline;
2991 display: inline;
2974 padding: 0;
2992 padding: 0;
2975 box-sizing: content-box;
2993 box-sizing: content-box;
2976 border-radius: 50%;
2994 border-radius: 50%;
2977 float: left;
2995 float: left;
2978 }
2996 }
2979
2997
2980 .user-hovercard-name {
2998 .user-hovercard-name {
2981 float: right;
2999 float: right;
2982 vertical-align: top;
3000 vertical-align: top;
2983 padding-left: 10px;
3001 padding-left: 10px;
2984 min-width: 150px;
3002 min-width: 150px;
2985 }
3003 }
2986
3004
2987 .user-hovercard-bio {
3005 .user-hovercard-bio {
2988 clear: both;
3006 clear: both;
2989 padding-top: 10px;
3007 padding-top: 10px;
2990 }
3008 }
2991
3009
2992 .user-hovercard-header {
3010 .user-hovercard-header {
2993 clear: both;
3011 clear: both;
2994 min-height: 10px;
3012 min-height: 10px;
2995 }
3013 }
2996
3014
2997 .user-hovercard-footer {
3015 .user-hovercard-footer {
2998 clear: both;
3016 clear: both;
2999 min-height: 10px;
3017 min-height: 10px;
3000 }
3018 }
3001
3019
3002 .user-group-hovercard {
3020 .user-group-hovercard {
3003 padding: 5px;
3021 padding: 5px;
3004 }
3022 }
3005
3023
3006 .user-group-hovercard-icon {
3024 .user-group-hovercard-icon {
3007 display: inline;
3025 display: inline;
3008 padding: 0;
3026 padding: 0;
3009 box-sizing: content-box;
3027 box-sizing: content-box;
3010 border-radius: 50%;
3028 border-radius: 50%;
3011 float: left;
3029 float: left;
3012 }
3030 }
3013
3031
3014 .user-group-hovercard-name {
3032 .user-group-hovercard-name {
3015 float: left;
3033 float: left;
3016 vertical-align: top;
3034 vertical-align: top;
3017 padding-left: 10px;
3035 padding-left: 10px;
3018 min-width: 150px;
3036 min-width: 150px;
3019 }
3037 }
3020
3038
3021 .user-group-hovercard-icon i {
3039 .user-group-hovercard-icon i {
3022 border: 1px solid @grey4;
3040 border: 1px solid @grey4;
3023 border-radius: 4px;
3041 border-radius: 4px;
3024 }
3042 }
3025
3043
3026 .user-group-hovercard-bio {
3044 .user-group-hovercard-bio {
3027 clear: both;
3045 clear: both;
3028 padding-top: 10px;
3046 padding-top: 10px;
3029 line-height: 1.0em;
3047 line-height: 1.0em;
3030 }
3048 }
3031
3049
3032 .user-group-hovercard-header {
3050 .user-group-hovercard-header {
3033 clear: both;
3051 clear: both;
3034 min-height: 10px;
3052 min-height: 10px;
3035 }
3053 }
3036
3054
3037 .user-group-hovercard-footer {
3055 .user-group-hovercard-footer {
3038 clear: both;
3056 clear: both;
3039 min-height: 10px;
3057 min-height: 10px;
3040 }
3058 }
3041
3059
3042 .pr-hovercard-header {
3060 .pr-hovercard-header {
3043 clear: both;
3061 clear: both;
3044 display: block;
3062 display: block;
3045 line-height: 20px;
3063 line-height: 20px;
3046 }
3064 }
3047
3065
3048 .pr-hovercard-user {
3066 .pr-hovercard-user {
3049 display: flex;
3067 display: flex;
3050 align-items: center;
3068 align-items: center;
3051 padding-left: 5px;
3069 padding-left: 5px;
3052 }
3070 }
3053
3071
3054 .pr-hovercard-title {
3072 .pr-hovercard-title {
3055 padding-top: 5px;
3073 padding-top: 5px;
3056 } No newline at end of file
3074 }
3075
3076 .action-divider {
3077 opacity: 0.5;
3078 }
3079
3080 .details-inline-block {
3081 display: inline-block;
3082 position: relative;
3083 }
3084
3085 .details-inline-block summary {
3086 list-style: none;
3087 }
3088
3089 details:not([open]) > :not(summary) {
3090 display: none !important;
3091 }
3092
3093 .details-reset > summary {
3094 list-style: none;
3095 }
3096
3097 .details-reset > summary::-webkit-details-marker {
3098 display: none;
3099 }
3100
3101 .details-dropdown {
3102 position: absolute;
3103 top: 100%;
3104 width: 185px;
3105 list-style: none;
3106 background-color: #fff;
3107 background-clip: padding-box;
3108 border: 1px solid @grey5;
3109 box-shadow: 0 8px 24px rgba(149, 157, 165, .2);
3110 left: -150px;
3111 text-align: left;
3112 z-index: 90;
3113 }
3114
3115 .dropdown-divider {
3116 display: block;
3117 height: 0;
3118 margin: 8px 0;
3119 border-top: 1px solid @grey5;
3120 }
3121
3122 .dropdown-item {
3123 display: block;
3124 padding: 4px 8px 4px 16px;
3125 overflow: hidden;
3126 text-overflow: ellipsis;
3127 white-space: nowrap;
3128 font-weight: normal;
3129 }
3130
3131 .right-sidebar {
3132 position: fixed;
3133 top: 0px;
3134 bottom: 0;
3135 right: 0;
3136
3137 background: #fafafa;
3138 z-index: 50;
3139 }
3140
3141 .right-sidebar {
3142 border-left: 1px solid @grey5;
3143 }
3144
3145 .right-sidebar.right-sidebar-expanded {
3146 width: 300px;
3147 overflow: scroll;
3148 }
3149
3150 .right-sidebar.right-sidebar-collapsed {
3151 width: 40px;
3152 padding: 0;
3153 display: block;
3154 overflow: hidden;
3155 }
3156
3157 .sidenav {
3158 float: right;
3159 will-change: min-height;
3160 background: #fafafa;
3161 width: 100%;
3162 }
3163
3164 .sidebar-toggle {
3165 height: 30px;
3166 text-align: center;
3167 margin: 15px 0px 0 0;
3168 }
3169
3170 .sidebar-toggle a {
3171
3172 }
3173
3174 .sidebar-content {
3175 margin-left: 15px;
3176 margin-right: 15px;
3177 }
3178
3179 .sidebar-heading {
3180 font-size: 1.2em;
3181 font-weight: 700;
3182 margin-top: 10px;
3183 }
3184
3185 .sidebar-element {
3186 margin-top: 20px;
3187 }
3188
3189 .right-sidebar-collapsed-state {
3190 display: flex;
3191 flex-direction: column;
3192 justify-content: center;
3193 align-items: center;
3194 padding: 0 10px;
3195 cursor: pointer;
3196 font-size: 1.3em;
3197 margin: 0 -15px;
3198 }
3199
3200 .right-sidebar-collapsed-state:hover {
3201 background-color: @grey5;
3202 }
3203
3204 .old-comments-marker {
3205 text-align: left;
3206 }
3207
3208 .old-comments-marker td {
3209 padding-top: 15px;
3210 border-bottom: 1px solid @grey5;
3211 }
@@ -1,872 +1,895 b''
1 // navigation.less
1 // navigation.less
2 // For use in RhodeCode applications;
2 // For use in RhodeCode applications;
3 // see style guide documentation for guidelines.
3 // see style guide documentation for guidelines.
4
4
5 // TOP MAIN DARK NAVIGATION
5 // TOP MAIN DARK NAVIGATION
6
6
7 .header .main_nav.horizontal-list {
7 .header .main_nav.horizontal-list {
8 float: right;
8 float: right;
9 color: @grey4;
9 color: @grey4;
10 > li {
10 > li {
11 a {
11 a {
12 color: @grey4;
12 color: @grey4;
13 }
13 }
14 }
14 }
15 }
15 }
16
16
17 // HEADER NAVIGATION
17 // HEADER NAVIGATION
18
18
19 .horizontal-list {
19 .horizontal-list {
20 display: block;
20 display: block;
21 margin: 0;
21 margin: 0;
22 padding: 0;
22 padding: 0;
23 -webkit-padding-start: 0;
23 -webkit-padding-start: 0;
24 text-align: left;
24 text-align: left;
25 font-size: @navigation-fontsize;
25 font-size: @navigation-fontsize;
26 color: @grey6;
26 color: @grey6;
27 z-index:10;
27 z-index:10;
28
28
29 li {
29 li {
30 line-height: 1em;
30 line-height: 1em;
31 list-style-type: none;
31 list-style-type: none;
32 margin: 0 20px 0 0;
32 margin: 0 20px 0 0;
33
33
34 a {
34 a {
35 padding: 0 .5em;
35 padding: 0 .5em;
36
36
37 &.menu_link_notifications {
37 &.menu_link_notifications {
38 .pill(7px,@rcblue);
38 .pill(7px,@rcblue);
39 display: inline;
39 display: inline;
40 margin: 0 7px 0 .7em;
40 margin: 0 7px 0 .7em;
41 font-size: @basefontsize;
41 font-size: @basefontsize;
42 color: white;
42 color: white;
43
43
44 &.empty {
44 &.empty {
45 background-color: @grey4;
45 background-color: @grey4;
46 }
46 }
47
47
48 &:hover {
48 &:hover {
49 background-color: @rcdarkblue;
49 background-color: @rcdarkblue;
50 }
50 }
51 }
51 }
52 }
52 }
53 .pill_container {
53 .pill_container {
54 margin: 1.25em 0px 0px 0px;
54 margin: 1.25em 0px 0px 0px;
55 float: right;
55 float: right;
56 }
56 }
57
57
58 &#quick_login_li {
58 &#quick_login_li {
59 &:hover {
59 &:hover {
60 color: @grey5;
60 color: @grey5;
61 }
61 }
62
62
63 a.menu_link_notifications {
63 a.menu_link_notifications {
64 color: white;
64 color: white;
65 }
65 }
66
66
67 .user {
67 .user {
68 padding-bottom: 10px;
68 padding-bottom: 10px;
69 }
69 }
70 }
70 }
71
71
72 &:before { content: none; }
72 &:before { content: none; }
73
73
74 &:last-child {
74 &:last-child {
75 .menulabel {
75 .menulabel {
76 padding-right: 0;
76 padding-right: 0;
77 border-right: none;
77 border-right: none;
78
78
79 .show_more {
79 .show_more {
80 padding-right: 0;
80 padding-right: 0;
81 }
81 }
82 }
82 }
83
83
84 &> a {
84 &> a {
85 border-bottom: none;
85 border-bottom: none;
86 }
86 }
87 }
87 }
88
88
89 &.open {
89 &.open {
90
90
91 a {
91 a {
92 color: white;
92 color: white;
93 }
93 }
94 }
94 }
95
95
96 &:focus {
96 &:focus {
97 outline: none;
97 outline: none;
98 }
98 }
99
99
100 ul li {
100 ul li {
101 display: block;
101 display: block;
102
102
103 &:last-child> a {
103 &:last-child> a {
104 border-bottom: none;
104 border-bottom: none;
105 }
105 }
106
106
107 ul li:last-child a {
107 ul li:last-child a {
108 /* we don't expect more then 3 levels of submenu and the third
108 /* we don't expect more then 3 levels of submenu and the third
109 level can have different html structure */
109 level can have different html structure */
110 border-bottom: none;
110 border-bottom: none;
111 }
111 }
112 }
112 }
113 }
113 }
114
114
115 > li {
115 > li {
116 float: left;
116 float: left;
117 display: block;
117 display: block;
118 padding: 0;
118 padding: 0;
119
119
120 > a,
120 > a,
121 &.has_select2 a {
121 &.has_select2 a {
122 display: block;
122 display: block;
123 padding: 10px 0;
123 padding: 10px 0;
124 }
124 }
125
125
126 .menulabel {
126 .menulabel {
127 line-height: 1em;
127 line-height: 1em;
128 // for this specifically we do not use a variable
128 // for this specifically we do not use a variable
129 }
129 }
130
130
131 .menulink-counter {
131 .menulink-counter {
132 border: 1px solid @grey2;
132 border: 1px solid @grey2;
133 border-radius: @border-radius;
133 border-radius: @border-radius;
134 background: @grey7;
134 background: @grey7;
135 display: inline-block;
135 display: inline-block;
136 padding: 0px 4px;
136 padding: 0px 4px;
137 text-align: center;
137 text-align: center;
138 font-size: 12px;
138 font-size: 12px;
139 }
139 }
140
140
141 .pr_notifications {
141 .pr_notifications {
142 padding-left: .5em;
142 padding-left: .5em;
143 }
143 }
144
144
145 .pr_notifications + .menulabel {
145 .pr_notifications + .menulabel {
146 display:inline;
146 display:inline;
147 padding-left: 0;
147 padding-left: 0;
148 }
148 }
149
149
150 &:hover,
150 &:hover,
151 &.open,
151 &.open,
152 &.active {
152 &.active {
153 a {
153 a {
154 color: @rcblue;
154 color: @rcblue;
155 }
155 }
156 }
156 }
157 }
157 }
158
158
159 pre {
159 pre {
160 margin: 0;
160 margin: 0;
161 padding: 0;
161 padding: 0;
162 }
162 }
163
163
164 .select2-container,
164 .select2-container,
165 .menulink.childs {
165 .menulink.childs {
166 position: relative;
166 position: relative;
167 }
167 }
168
168
169 .menulink {
169 .menulink {
170 &.disabled {
170 &.disabled {
171 color: @grey3;
171 color: @grey3;
172 cursor: default;
172 cursor: default;
173 opacity: 0.5;
173 opacity: 0.5;
174 }
174 }
175 }
175 }
176
176
177 #quick_login {
177 #quick_login {
178
178
179 li a {
179 li a {
180 padding: .5em 0;
180 padding: .5em 0;
181 border-bottom: none;
181 border-bottom: none;
182 color: @grey2;
182 color: @grey2;
183
183
184 &:hover { color: @rcblue; }
184 &:hover { color: @rcblue; }
185 }
185 }
186 }
186 }
187
187
188 #quick_login_link {
188 #quick_login_link {
189 display: inline-block;
189 display: inline-block;
190
190
191 .gravatar {
191 .gravatar {
192 border: 1px solid @grey5;
192 border: 1px solid @grey5;
193 }
193 }
194
194
195 .gravatar-login {
195 .gravatar-login {
196 height: 20px;
196 height: 20px;
197 width: 20px;
197 width: 20px;
198 margin: -8px 0;
198 margin: -8px 0;
199 padding: 0;
199 padding: 0;
200 }
200 }
201
201
202 &:hover .user {
202 &:hover .user {
203 color: @grey6;
203 color: @grey6;
204 }
204 }
205 }
205 }
206 }
206 }
207 .header .horizontal-list {
207 .header .horizontal-list {
208
208
209 li {
209 li {
210
210
211 &#quick_login_li {
211 &#quick_login_li {
212 padding-left: .5em;
212 padding-left: .5em;
213 margin-right: 0px;
213 margin-right: 0px;
214
214
215 &:hover #quick_login_link {
215 &:hover #quick_login_link {
216 color: inherit;
216 color: inherit;
217 }
217 }
218
218
219 .menu_link_user {
219 .menu_link_user {
220 padding: 0 2px;
220 padding: 0 2px;
221 }
221 }
222 }
222 }
223 list-style-type: none;
223 list-style-type: none;
224 }
224 }
225
225
226 > li {
226 > li {
227
227
228 a {
228 a {
229 padding: 18px 0 12px 0;
229 padding: 18px 0 12px 0;
230 color: @nav-grey;
230 color: @nav-grey;
231
231
232 &.menu_link_notifications {
232 &.menu_link_notifications {
233 padding: 1px 8px;
233 padding: 1px 8px;
234 }
234 }
235 }
235 }
236
236
237 &:hover,
237 &:hover,
238 &.open,
238 &.open,
239 &.active {
239 &.active {
240 .pill_container a {
240 .pill_container a {
241 // don't select text for the pill container, it has it' own
241 // don't select text for the pill container, it has it' own
242 // hover behaviour
242 // hover behaviour
243 color: @nav-grey;
243 color: @nav-grey;
244 }
244 }
245 }
245 }
246
246
247 &:hover,
247 &:hover,
248 &.open,
248 &.open,
249 &.active {
249 &.active {
250 a {
250 a {
251 color: @grey6;
251 color: @grey6;
252 }
252 }
253 }
253 }
254
254
255 .select2-dropdown-open a {
255 .select2-dropdown-open a {
256 color: @grey6;
256 color: @grey6;
257 }
257 }
258
258
259 .repo-switcher {
259 .repo-switcher {
260 padding-left: 0;
260 padding-left: 0;
261
261
262 .menulabel {
262 .menulabel {
263 padding-left: 0;
263 padding-left: 0;
264 }
264 }
265 }
265 }
266 }
266 }
267
267
268 li ul li {
268 li ul li {
269 background-color:@grey2;
269 background-color:@grey2;
270
270
271 a {
271 a {
272 padding: .5em 0;
272 padding: .5em 0;
273 border-bottom: @border-thickness solid @border-default-color;
273 border-bottom: @border-thickness solid @border-default-color;
274 color: @grey6;
274 color: @grey6;
275 }
275 }
276
276
277 &:last-child a, &.last a{
277 &:last-child a, &.last a{
278 border-bottom: none;
278 border-bottom: none;
279 }
279 }
280
280
281 &:hover {
281 &:hover {
282 background-color: @grey3;
282 background-color: @grey3;
283 }
283 }
284 }
284 }
285
285
286 .submenu {
286 .submenu {
287 margin-top: 5px;
287 margin-top: 5px;
288 }
288 }
289 }
289 }
290
290
291 // SUBMENUS
291 // SUBMENUS
292 .navigation .submenu {
292 .navigation .submenu {
293 display: none;
293 display: none;
294 }
294 }
295
295
296 .navigation li.open {
296 .navigation li.open {
297 .submenu {
297 .submenu {
298 display: block;
298 display: block;
299 }
299 }
300 }
300 }
301
301
302 .navigation li:last-child .submenu {
302 .navigation li:last-child .submenu {
303 right: auto;
303 right: auto;
304 left: 0;
304 left: 0;
305 border: 1px solid @grey5;
305 border: 1px solid @grey5;
306 background: @white;
306 background: @white;
307 box-shadow: @dropdown-shadow;
307 box-shadow: @dropdown-shadow;
308 }
308 }
309
309
310 .submenu {
310 .submenu {
311 position: absolute;
311 position: absolute;
312 top: 100%;
312 top: 100%;
313 left: 0;
313 left: 0;
314 min-width: 180px;
314 min-width: 180px;
315 margin: 2px 0 0;
315 margin: 2px 0 0;
316 padding: 0;
316 padding: 0;
317 text-align: left;
317 text-align: left;
318 font-family: @text-light;
318 font-family: @text-light;
319 border-radius: @border-radius;
319 border-radius: @border-radius;
320 z-index: 20;
320 z-index: 20;
321
321
322 li {
322 li {
323 display: block;
323 display: block;
324 margin: 0;
324 margin: 0;
325 padding: 0 .5em;
325 padding: 0 .5em;
326 line-height: 1em;
326 line-height: 1em;
327 color: @grey3;
327 color: @grey3;
328 background-color: @white;
328 background-color: @white;
329 list-style-type: none;
329 list-style-type: none;
330
330
331 a {
331 a {
332 display: block;
332 display: block;
333 width: 100%;
333 width: 100%;
334 padding: .5em 0;
334 padding: .5em 0;
335 border-right: none;
335 border-right: none;
336 border-bottom: @border-thickness solid white;
336 border-bottom: @border-thickness solid white;
337 color: @grey3;
337 color: @grey3;
338 }
338 }
339
339
340 ul {
340 ul {
341 display: none;
341 display: none;
342 position: absolute;
342 position: absolute;
343 top: 0;
343 top: 0;
344 right: 100%;
344 right: 100%;
345 padding: 0;
345 padding: 0;
346 z-index: 30;
346 z-index: 30;
347 }
347 }
348 &:hover {
348 &:hover {
349 background-color: @grey7;
349 background-color: @grey7;
350 -webkit-transition: background .3s;
350 -webkit-transition: background .3s;
351 -moz-transition: background .3s;
351 -moz-transition: background .3s;
352 -o-transition: background .3s;
352 -o-transition: background .3s;
353 transition: background .3s;
353 transition: background .3s;
354
354
355 ul {
355 ul {
356 display: block;
356 display: block;
357 }
357 }
358 }
358 }
359 }
359 }
360
360
361 }
361 }
362
362
363
363
364
364
365
365
366 // repo dropdown
366 // repo dropdown
367 .quick_repo_menu {
367 .quick_repo_menu {
368 width: 15px;
368 width: 15px;
369 text-align: center;
369 text-align: center;
370 position: relative;
370 position: relative;
371 cursor: pointer;
371 cursor: pointer;
372
372
373 div {
373 div {
374 overflow: visible !important;
374 overflow: visible !important;
375 }
375 }
376
376
377 &.sorting {
377 &.sorting {
378 cursor: auto;
378 cursor: auto;
379 }
379 }
380
380
381 &:hover {
381 &:hover {
382 .menu_items_container {
382 .menu_items_container {
383 position: absolute;
383 position: absolute;
384 display: block;
384 display: block;
385 }
385 }
386 .menu_items {
386 .menu_items {
387 display: block;
387 display: block;
388 }
388 }
389 }
389 }
390
390
391 i {
391 i {
392 margin: 0;
392 margin: 0;
393 color: @grey4;
393 color: @grey4;
394 }
394 }
395
395
396 .menu_items_container {
396 .menu_items_container {
397 position: absolute;
397 position: absolute;
398 top: 0;
398 top: 0;
399 left: 100%;
399 left: 100%;
400 margin: 0;
400 margin: 0;
401 padding: 0;
401 padding: 0;
402 list-style: none;
402 list-style: none;
403 background-color: @grey6;
403 background-color: @grey6;
404 z-index: 999;
404 z-index: 999;
405 text-align: left;
405 text-align: left;
406
406
407 a {
407 a {
408 color: @grey2;
408 color: @grey2;
409 }
409 }
410
410
411 ul.menu_items {
411 ul.menu_items {
412 margin: 0;
412 margin: 0;
413 padding: 0;
413 padding: 0;
414 }
414 }
415
415
416 li {
416 li {
417 margin: 0;
417 margin: 0;
418 padding: 0;
418 padding: 0;
419 line-height: 1em;
419 line-height: 1em;
420 list-style-type: none;
420 list-style-type: none;
421
421
422 a {
422 a {
423 display: block;
423 display: block;
424 height: 16px;
424 height: 16px;
425 padding: 8px; //must add up to td height (28px)
425 padding: 8px; //must add up to td height (28px)
426 width: 120px; // set width
426 width: 120px; // set width
427
427
428 &:hover {
428 &:hover {
429 background-color: @grey5;
429 background-color: @grey5;
430 -webkit-transition: background .3s;
430 -webkit-transition: background .3s;
431 -moz-transition: background .3s;
431 -moz-transition: background .3s;
432 -o-transition: background .3s;
432 -o-transition: background .3s;
433 transition: background .3s;
433 transition: background .3s;
434 }
434 }
435 }
435 }
436 }
436 }
437 }
437 }
438 }
438 }
439
439
440
440
441 // new objects main action
441 // new objects main action
442 .action-menu {
442 .action-menu {
443 left: auto;
443 left: auto;
444 right: 0;
444 right: 0;
445 padding: 12px;
445 padding: 12px;
446 z-index: 999;
446 z-index: 999;
447 overflow: hidden;
447 overflow: hidden;
448 background-color: #fff;
448 background-color: #fff;
449 border: 1px solid @grey5;
449 border: 1px solid @grey5;
450 color: @grey2;
450 color: @grey2;
451 box-shadow: @dropdown-shadow;
451 box-shadow: @dropdown-shadow;
452
452
453 .submenu-title {
453 .submenu-title {
454 font-weight: bold;
454 font-weight: bold;
455 }
455 }
456
456
457 .submenu-title:not(:first-of-type) {
457 .submenu-title:not(:first-of-type) {
458 padding-top: 10px;
458 padding-top: 10px;
459 }
459 }
460
460
461 &.submenu {
461 &.submenu {
462 min-width: 200px;
462 min-width: 200px;
463
463
464 ol {
464 ol {
465 padding:0;
465 padding:0;
466 }
466 }
467
467
468 li {
468 li {
469 display: block;
469 display: block;
470 margin: 0;
470 margin: 0;
471 padding: .2em .5em;
471 padding: .2em .5em;
472 line-height: 1em;
472 line-height: 1em;
473
473
474 background-color: #fff;
474 background-color: #fff;
475 list-style-type: none;
475 list-style-type: none;
476
476
477 a {
477 a {
478 padding: 4px;
478 padding: 4px;
479 color: @grey4 !important;
479 color: @grey4 !important;
480 border-bottom: none;
480 border-bottom: none;
481 }
481 }
482 }
482 }
483 li:not(.submenu-title) a:hover{
483 li:not(.submenu-title) a:hover{
484 color: @grey2 !important;
484 color: @grey2 !important;
485 }
485 }
486 }
486 }
487 }
487 }
488
488
489
489
490 // Header Repository Switcher
490 // Header Repository Switcher
491 // Select2 Dropdown
491 // Select2 Dropdown
492 #select2-drop.select2-drop.repo-switcher-dropdown {
492 #select2-drop.select2-drop.repo-switcher-dropdown {
493 width: auto !important;
493 width: auto !important;
494 margin-top: 5px;
494 margin-top: 5px;
495 padding: 1em 0;
495 padding: 1em 0;
496 text-align: left;
496 text-align: left;
497 .border-radius-bottom(@border-radius);
497 .border-radius-bottom(@border-radius);
498 border-color: transparent;
498 border-color: transparent;
499 color: @grey6;
499 color: @grey6;
500 background-color: @grey2;
500 background-color: @grey2;
501
501
502 input {
502 input {
503 min-width: 90%;
503 min-width: 90%;
504 }
504 }
505
505
506 ul.select2-result-sub {
506 ul.select2-result-sub {
507
507
508 li {
508 li {
509 line-height: 1em;
509 line-height: 1em;
510
510
511 &:hover,
511 &:hover,
512 &.select2-highlighted {
512 &.select2-highlighted {
513 background-color: @grey3;
513 background-color: @grey3;
514 }
514 }
515 }
515 }
516
516
517 &:before { content: none; }
517 &:before { content: none; }
518 }
518 }
519
519
520 ul.select2-results {
520 ul.select2-results {
521 min-width: 200px;
521 min-width: 200px;
522 margin: 0;
522 margin: 0;
523 padding: 0;
523 padding: 0;
524 list-style-type: none;
524 list-style-type: none;
525 overflow-x: visible;
525 overflow-x: visible;
526 overflow-y: scroll;
526 overflow-y: scroll;
527
527
528 li {
528 li {
529 padding: 0 8px;
529 padding: 0 8px;
530 line-height: 1em;
530 line-height: 1em;
531 color: @grey6;
531 color: @grey6;
532
532
533 &>.select2-result-label {
533 &>.select2-result-label {
534 padding: 8px 0;
534 padding: 8px 0;
535 border-bottom: @border-thickness solid @grey3;
535 border-bottom: @border-thickness solid @grey3;
536 white-space: nowrap;
536 white-space: nowrap;
537 color: @grey5;
537 color: @grey5;
538 cursor: pointer;
538 cursor: pointer;
539 }
539 }
540
540
541 &.select2-result-with-children {
541 &.select2-result-with-children {
542 margin: 0;
542 margin: 0;
543 padding: 0;
543 padding: 0;
544 }
544 }
545
545
546 &.select2-result-unselectable > .select2-result-label {
546 &.select2-result-unselectable > .select2-result-label {
547 margin: 0 8px;
547 margin: 0 8px;
548 }
548 }
549
549
550 }
550 }
551 }
551 }
552
552
553 ul.select2-result-sub {
553 ul.select2-result-sub {
554 margin: 0;
554 margin: 0;
555 padding: 0;
555 padding: 0;
556
556
557 li {
557 li {
558 display: block;
558 display: block;
559 margin: 0;
559 margin: 0;
560 border-right: none;
560 border-right: none;
561 line-height: 1em;
561 line-height: 1em;
562 font-family: @text-light;
562 font-family: @text-light;
563 color: @grey2;
563 color: @grey2;
564 list-style-type: none;
564 list-style-type: none;
565
565
566 &:hover {
566 &:hover {
567 background-color: @grey3;
567 background-color: @grey3;
568 }
568 }
569 }
569 }
570 }
570 }
571 }
571 }
572
572
573
573
574 #context-bar {
574 #context-bar {
575 display: block;
575 display: block;
576 margin: 0 auto 20px 0;
576 margin: 0 auto 20px 0;
577 padding: 0 @header-padding;
577 padding: 0 @header-padding;
578 background-color: @grey7;
578 background-color: @grey7;
579 border-bottom: 1px solid @grey5;
579 border-bottom: 1px solid @grey5;
580
580
581 .clear {
581 .clear {
582 clear: both;
582 clear: both;
583 }
583 }
584 }
584 }
585
585
586 ul#context-pages {
586 ul#context-pages {
587 li {
587 li {
588 list-style-type: none;
588 list-style-type: none;
589
589
590 a {
590 a {
591 color: @grey2;
591 color: @grey2;
592
592
593 &:hover {
593 &:hover {
594 color: @grey1;
594 color: @grey1;
595 }
595 }
596 }
596 }
597
597
598 &.active {
598 &.active {
599 // special case, non-variable color
599 // special case, non-variable color
600 border-bottom: 2px solid @rcblue;
600 border-bottom: 2px solid @rcblue;
601
601
602 a {
602 a {
603 color: @rcblue;
603 color: @rcblue;
604 }
604 }
605 }
605 }
606 }
606 }
607 }
607 }
608
608
609 // PAGINATION
609 // PAGINATION
610
610
611 .pagination {
611 .pagination {
612 border: @border-thickness solid @grey5;
612 border: @border-thickness solid @grey5;
613 color: @grey2;
613 color: @grey2;
614 box-shadow: @button-shadow;
614 box-shadow: @button-shadow;
615
615
616 .current {
616 .current {
617 color: @grey4;
617 color: @grey4;
618 }
618 }
619 }
619 }
620
620
621 .dataTables_processing {
621 .dataTables_processing {
622 text-align: center;
622 text-align: center;
623 font-size: 1.1em;
623 font-size: 1.1em;
624 position: relative;
624 position: relative;
625 top: 95px;
625 top: 95px;
626 height: 0;
626 height: 0;
627 }
627 }
628
628
629 .dataTables_paginate,
629 .dataTables_paginate,
630 .pagination-wh {
630 .pagination-wh {
631 text-align: center;
631 text-align: center;
632 display: inline-block;
632 display: inline-block;
633 border-left: 1px solid @grey5;
633 border-left: 1px solid @grey5;
634 float: none;
634 float: none;
635 overflow: hidden;
635 overflow: hidden;
636 box-shadow: @button-shadow;
636 box-shadow: @button-shadow;
637
637
638 .paginate_button, .pager_curpage,
638 .paginate_button, .pager_curpage,
639 .pager_link, .pg-previous, .pg-next, .pager_dotdot {
639 .pager_link, .pg-previous, .pg-next, .pager_dotdot {
640 display: inline-block;
640 display: inline-block;
641 padding: @menupadding/4 @menupadding;
641 padding: @menupadding/4 @menupadding;
642 border: 1px solid @grey5;
642 border: 1px solid @grey5;
643 margin-left: -1px;
643 margin-left: -1px;
644 color: @grey2;
644 color: @grey2;
645 cursor: pointer;
645 cursor: pointer;
646 float: left;
646 float: left;
647 font-weight: 600;
647 font-weight: 600;
648 white-space: nowrap;
648 white-space: nowrap;
649 vertical-align: middle;
649 vertical-align: middle;
650 user-select: none;
650 user-select: none;
651 min-width: 15px;
651 min-width: 15px;
652
652
653 &:hover {
653 &:hover {
654 color: @rcdarkblue;
654 color: @rcdarkblue;
655 }
655 }
656 }
656 }
657
657
658 .paginate_button.disabled,
658 .paginate_button.disabled,
659 .disabled {
659 .disabled {
660 color: @grey3;
660 color: @grey3;
661 cursor: default;
661 cursor: default;
662 opacity: 0.5;
662 opacity: 0.5;
663 }
663 }
664
664
665 .paginate_button.current, .pager_curpage {
665 .paginate_button.current, .pager_curpage {
666 background: @rcblue;
666 background: @rcblue;
667 border-color: @rcblue;
667 border-color: @rcblue;
668 color: @white;
668 color: @white;
669 }
669 }
670
670
671 .ellipsis {
671 .ellipsis {
672 display: inline-block;
672 display: inline-block;
673 text-align: left;
673 text-align: left;
674 padding: @menupadding/4 @menupadding;
674 padding: @menupadding/4 @menupadding;
675 border: 1px solid @grey5;
675 border: 1px solid @grey5;
676 border-left: 0;
676 border-left: 0;
677 float: left;
677 float: left;
678 }
678 }
679 }
679 }
680
680
681 // SIDEBAR
681 // SIDEBAR
682
682
683 .sidebar {
683 .sidebar {
684 .block-left;
684 .block-left;
685 clear: left;
685 clear: left;
686 max-width: @sidebar-width;
686 max-width: @sidebar-width;
687 margin-right: @sidebarpadding;
687 margin-right: @sidebarpadding;
688 padding-right: @sidebarpadding;
688 padding-right: @sidebarpadding;
689 font-family: @text-regular;
689 font-family: @text-regular;
690 color: @grey1;
690 color: @grey1;
691
691
692 .nav-pills {
692 .nav-pills {
693 margin: 0;
693 margin: 0;
694 }
694 }
695
695
696 .nav {
696 .nav {
697 list-style: none;
697 list-style: none;
698 padding: 0;
698 padding: 0;
699
699
700 li {
700 li {
701 padding-bottom: @menupadding;
701 padding-bottom: @menupadding;
702 line-height: 1em;
702 line-height: 1em;
703 color: @grey4;
703 color: @grey4;
704 list-style-type: none;
704 list-style-type: none;
705
705
706 &.active a {
706 &.active a {
707 color: @grey2;
707 color: @grey2;
708 }
708 }
709
709
710 a {
710 a {
711 color: @grey4;
711 color: @grey4;
712 }
712 }
713 }
713 }
714
714
715 }
715 }
716 }
716 }
717
717
718 .main_filter_help_box {
718 .main_filter_help_box {
719 padding: 7px 7px;
719 padding: 7px 7px;
720 display: inline-block;
720 display: inline-block;
721 vertical-align: top;
721 vertical-align: top;
722 background: inherit;
722 background: inherit;
723 position: absolute;
723 position: absolute;
724 right: 0;
724 right: 0;
725 top: 9px;
725 top: 9px;
726 }
726 }
727
727
728 .main_filter_input_box {
728 .main_filter_input_box {
729 display: inline-block;
729 display: inline-block;
730
730
731 .searchItems {
731 .searchItems {
732 display:flex;
732 display:flex;
733 background: @black;
733 background: @black;
734 padding: 0px;
734 padding: 0px;
735 border-radius: 3px;
735 border-radius: 3px;
736 border: 1px solid @black;
736 border: 1px solid @black;
737
737
738 a {
738 a {
739 border: none !important;
739 border: none !important;
740 }
740 }
741 }
741 }
742
742
743 .searchTag {
743 .searchTag {
744 line-height: 28px;
744 line-height: 28px;
745 padding: 0 5px;
745 padding: 0 5px;
746
746
747 .tag {
747 .tag {
748 color: @grey5;
748 color: @grey5;
749 border-color: @grey2;
749 border-color: @grey2;
750 background: @grey1;
750 background: @grey1;
751 }
751 }
752 }
752 }
753
753
754 .searchTagFilter {
754 .searchTagFilter {
755 background-color: @black !important;
755 background-color: @black !important;
756 margin-right: 0;
756 margin-right: 0;
757 }
757 }
758 .searchTagIcon {
758 .searchTagIcon {
759 margin: 0;
759 margin: 0;
760 background: @black !important;
760 background: @black !important;
761 }
761 }
762 .searchTagHelp {
762 .searchTagHelp {
763 background-color: @grey1 !important;
763 background-color: @grey1 !important;
764 margin: 0;
764 margin: 0;
765 }
765 }
766 .searchTagHelp:hover {
766 .searchTagHelp:hover {
767 background-color: @grey1 !important;
767 background-color: @grey1 !important;
768 }
768 }
769 .searchTagInput {
769 .searchTagInput {
770 background-color: @grey1 !important;
770 background-color: @grey1 !important;
771 margin-right: 0;
771 margin-right: 0;
772 }
772 }
773 }
773 }
774
774
775 .main_filter_box {
775 .main_filter_box {
776 margin: 9px 0 0 0;
776 margin: 9px 0 0 0;
777 }
777 }
778
778
779 #main_filter_help {
779 #main_filter_help {
780 background: @grey1;
780 background: @grey1;
781 border: 1px solid black;
781 border: 1px solid black;
782 position: absolute;
782 position: absolute;
783 white-space: pre;
783 white-space: pre;
784 z-index: 9999;
784 z-index: 9999;
785 color: @nav-grey;
785 color: @nav-grey;
786 padding: 0 10px;
786 padding: 0 10px;
787 }
787 }
788
788
789 input {
789 input {
790
790
791 &.main_filter_input {
791 &.main_filter_input {
792 padding: 5px 10px;
792 padding: 5px 10px;
793 min-width: 340px;
793
794 color: @grey7;
794 color: @grey7;
795 background: @black;
795 background: @black;
796 min-height: 18px;
796 min-height: 18px;
797 border: 0;
797 border: 0;
798
798
799 &:active {
799 &:active {
800 color: @grey2 !important;
800 color: @grey2 !important;
801 background: white !important;
801 background: white !important;
802 }
802 }
803
803 &:focus {
804 &:focus {
804 color: @grey2 !important;
805 color: @grey2 !important;
805 background: white !important;
806 background: white !important;
806 }
807 }
808
809 min-width: 360px;
810
811 @media screen and (max-width: 1600px) {
812 min-width: 300px;
813 }
814 @media screen and (max-width: 1500px) {
815 min-width: 280px;
816 }
817 @media screen and (max-width: 1400px) {
818 min-width: 260px;
819 }
820 @media screen and (max-width: 1300px) {
821 min-width: 240px;
822 }
823 @media screen and (max-width: 1200px) {
824 min-width: 220px;
825 }
826 @media screen and (max-width: 720px) {
827 min-width: 140px;
828 }
807 }
829 }
830
808 }
831 }
809
832
810
833
811
834
812 .main_filter_input::placeholder {
835 .main_filter_input::placeholder {
813 color: @nav-grey;
836 color: @nav-grey;
814 opacity: 1;
837 opacity: 1;
815 }
838 }
816
839
817 .notice-box {
840 .notice-box {
818 display:block !important;
841 display:block !important;
819 padding: 9px 0 !important;
842 padding: 9px 0 !important;
820 }
843 }
821
844
822 .menulabel-notice {
845 .menulabel-notice {
823
846
824 padding:7px 10px;
847 padding:7px 10px;
825
848
826 &.notice-warning {
849 &.notice-warning {
827 border: 1px solid @color3;
850 border: 1px solid @color3;
828 .notice-color-warning
851 .notice-color-warning
829 }
852 }
830 &.notice-error {
853 &.notice-error {
831 border: 1px solid @color5;
854 border: 1px solid @color5;
832 .notice-color-error
855 .notice-color-error
833 }
856 }
834 &.notice-info {
857 &.notice-info {
835 border: 1px solid @color1;
858 border: 1px solid @color1;
836 .notice-color-info
859 .notice-color-info
837 }
860 }
838 }
861 }
839
862
840 .notice-messages-container {
863 .notice-messages-container {
841 position: absolute;
864 position: absolute;
842 top: 45px;
865 top: 45px;
843 }
866 }
844
867
845 .notice-messages {
868 .notice-messages {
846 display: block;
869 display: block;
847 position: relative;
870 position: relative;
848 z-index: 300;
871 z-index: 300;
849 min-width: 500px;
872 min-width: 500px;
850 max-width: 500px;
873 max-width: 500px;
851 min-height: 100px;
874 min-height: 100px;
852 margin-top: 4px;
875 margin-top: 4px;
853 margin-bottom: 24px;
876 margin-bottom: 24px;
854 font-size: 14px;
877 font-size: 14px;
855 font-weight: 400;
878 font-weight: 400;
856 padding: 8px 0;
879 padding: 8px 0;
857 background-color: #fff;
880 background-color: #fff;
858 border: 1px solid @grey4;
881 border: 1px solid @grey4;
859 box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.07);
882 box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.07);
860 }
883 }
861
884
862 .notice-color-warning {
885 .notice-color-warning {
863 color: @color3;
886 color: @color3;
864 }
887 }
865
888
866 .notice-color-error {
889 .notice-color-error {
867 color: @color5;
890 color: @color5;
868 }
891 }
869
892
870 .notice-color-info {
893 .notice-color-info {
871 color: @color1;
894 color: @color1;
872 }
895 }
@@ -1,291 +1,293 b''
1 @font-face {
1 @font-face {
2 font-family: 'rcicons';
2 font-family: 'rcicons';
3
3
4 src: url('../fonts/RCIcons/rcicons.eot?44705679');
4 src: url('../fonts/RCIcons/rcicons.eot?44705679');
5 src: url('../fonts/RCIcons/rcicons.eot?44705679#iefix') format('embedded-opentype'),
5 src: url('../fonts/RCIcons/rcicons.eot?44705679#iefix') format('embedded-opentype'),
6 url('../fonts/RCIcons/rcicons.woff2?44705679') format('woff2'),
6 url('../fonts/RCIcons/rcicons.woff2?44705679') format('woff2'),
7 url('../fonts/RCIcons/rcicons.woff?44705679') format('woff'),
7 url('../fonts/RCIcons/rcicons.woff?44705679') format('woff'),
8 url('../fonts/RCIcons/rcicons.ttf?44705679') format('truetype'),
8 url('../fonts/RCIcons/rcicons.ttf?44705679') format('truetype'),
9 url('../fonts/RCIcons/rcicons.svg?44705679#rcicons') format('svg');
9 url('../fonts/RCIcons/rcicons.svg?44705679#rcicons') format('svg');
10
10
11 font-weight: normal;
11 font-weight: normal;
12 font-style: normal;
12 font-style: normal;
13 }
13 }
14 /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
14 /* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */
15 /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
15 /* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */
16 /*
16 /*
17 @media screen and (-webkit-min-device-pixel-ratio:0) {
17 @media screen and (-webkit-min-device-pixel-ratio:0) {
18 @font-face {
18 @font-face {
19 font-family: 'rcicons';
19 font-family: 'rcicons';
20 src: url('../fonts/RCIcons/rcicons.svg?74666722#rcicons') format('svg');
20 src: url('../fonts/RCIcons/rcicons.svg?74666722#rcicons') format('svg');
21 }
21 }
22 }
22 }
23 */
23 */
24
24
25 [class^="icon-"]:before, [class*=" icon-"]:before {
25 [class^="icon-"]:before, [class*=" icon-"]:before {
26 font-family: "rcicons";
26 font-family: "rcicons";
27 font-style: normal;
27 font-style: normal;
28 font-weight: normal;
28 font-weight: normal;
29 speak: none;
29 speak: none;
30
30
31 display: inline-block;
31 display: inline-block;
32 text-decoration: inherit;
32 text-decoration: inherit;
33 width: 1em;
33 width: 1em;
34 margin-right: .2em;
34 margin-right: .2em;
35 text-align: center;
35 text-align: center;
36 /* opacity: .8; */
36 /* opacity: .8; */
37
37
38 /* For safety - reset parent styles, that can break glyph codes*/
38 /* For safety - reset parent styles, that can break glyph codes*/
39 font-variant: normal;
39 font-variant: normal;
40 text-transform: none;
40 text-transform: none;
41
41
42 /* fix buttons height, for twitter bootstrap */
42 /* fix buttons height, for twitter bootstrap */
43 line-height: 1em;
43 line-height: 1em;
44
44
45 /* Animation center compensation - margins should be symmetric */
45 /* Animation center compensation - margins should be symmetric */
46 /* remove if not needed */
46 /* remove if not needed */
47 margin-left: .2em;
47 margin-left: .2em;
48
48
49 /* you can be more comfortable with increased icons size */
49 /* you can be more comfortable with increased icons size */
50 /* font-size: 120%; */
50 /* font-size: 120%; */
51
51
52 /* Font smoothing. That was taken from TWBS */
52 /* Font smoothing. That was taken from TWBS */
53 -webkit-font-smoothing: antialiased;
53 -webkit-font-smoothing: antialiased;
54 -moz-osx-font-smoothing: grayscale;
54 -moz-osx-font-smoothing: grayscale;
55
55
56 /* Uncomment for 3D effect */
56 /* Uncomment for 3D effect */
57 /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
57 /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */
58 }
58 }
59
59
60 .animate-spin {
60 .animate-spin {
61 -moz-animation: spin 2s infinite linear;
61 -moz-animation: spin 2s infinite linear;
62 -o-animation: spin 2s infinite linear;
62 -o-animation: spin 2s infinite linear;
63 -webkit-animation: spin 2s infinite linear;
63 -webkit-animation: spin 2s infinite linear;
64 animation: spin 2s infinite linear;
64 animation: spin 2s infinite linear;
65 display: inline-block;
65 display: inline-block;
66 }
66 }
67 @-moz-keyframes spin {
67 @-moz-keyframes spin {
68 0% {
68 0% {
69 -moz-transform: rotate(0deg);
69 -moz-transform: rotate(0deg);
70 -o-transform: rotate(0deg);
70 -o-transform: rotate(0deg);
71 -webkit-transform: rotate(0deg);
71 -webkit-transform: rotate(0deg);
72 transform: rotate(0deg);
72 transform: rotate(0deg);
73 }
73 }
74
74
75 100% {
75 100% {
76 -moz-transform: rotate(359deg);
76 -moz-transform: rotate(359deg);
77 -o-transform: rotate(359deg);
77 -o-transform: rotate(359deg);
78 -webkit-transform: rotate(359deg);
78 -webkit-transform: rotate(359deg);
79 transform: rotate(359deg);
79 transform: rotate(359deg);
80 }
80 }
81 }
81 }
82 @-webkit-keyframes spin {
82 @-webkit-keyframes spin {
83 0% {
83 0% {
84 -moz-transform: rotate(0deg);
84 -moz-transform: rotate(0deg);
85 -o-transform: rotate(0deg);
85 -o-transform: rotate(0deg);
86 -webkit-transform: rotate(0deg);
86 -webkit-transform: rotate(0deg);
87 transform: rotate(0deg);
87 transform: rotate(0deg);
88 }
88 }
89
89
90 100% {
90 100% {
91 -moz-transform: rotate(359deg);
91 -moz-transform: rotate(359deg);
92 -o-transform: rotate(359deg);
92 -o-transform: rotate(359deg);
93 -webkit-transform: rotate(359deg);
93 -webkit-transform: rotate(359deg);
94 transform: rotate(359deg);
94 transform: rotate(359deg);
95 }
95 }
96 }
96 }
97 @-o-keyframes spin {
97 @-o-keyframes spin {
98 0% {
98 0% {
99 -moz-transform: rotate(0deg);
99 -moz-transform: rotate(0deg);
100 -o-transform: rotate(0deg);
100 -o-transform: rotate(0deg);
101 -webkit-transform: rotate(0deg);
101 -webkit-transform: rotate(0deg);
102 transform: rotate(0deg);
102 transform: rotate(0deg);
103 }
103 }
104
104
105 100% {
105 100% {
106 -moz-transform: rotate(359deg);
106 -moz-transform: rotate(359deg);
107 -o-transform: rotate(359deg);
107 -o-transform: rotate(359deg);
108 -webkit-transform: rotate(359deg);
108 -webkit-transform: rotate(359deg);
109 transform: rotate(359deg);
109 transform: rotate(359deg);
110 }
110 }
111 }
111 }
112 @-ms-keyframes spin {
112 @-ms-keyframes spin {
113 0% {
113 0% {
114 -moz-transform: rotate(0deg);
114 -moz-transform: rotate(0deg);
115 -o-transform: rotate(0deg);
115 -o-transform: rotate(0deg);
116 -webkit-transform: rotate(0deg);
116 -webkit-transform: rotate(0deg);
117 transform: rotate(0deg);
117 transform: rotate(0deg);
118 }
118 }
119
119
120 100% {
120 100% {
121 -moz-transform: rotate(359deg);
121 -moz-transform: rotate(359deg);
122 -o-transform: rotate(359deg);
122 -o-transform: rotate(359deg);
123 -webkit-transform: rotate(359deg);
123 -webkit-transform: rotate(359deg);
124 transform: rotate(359deg);
124 transform: rotate(359deg);
125 }
125 }
126 }
126 }
127 @keyframes spin {
127 @keyframes spin {
128 0% {
128 0% {
129 -moz-transform: rotate(0deg);
129 -moz-transform: rotate(0deg);
130 -o-transform: rotate(0deg);
130 -o-transform: rotate(0deg);
131 -webkit-transform: rotate(0deg);
131 -webkit-transform: rotate(0deg);
132 transform: rotate(0deg);
132 transform: rotate(0deg);
133 }
133 }
134
134
135 100% {
135 100% {
136 -moz-transform: rotate(359deg);
136 -moz-transform: rotate(359deg);
137 -o-transform: rotate(359deg);
137 -o-transform: rotate(359deg);
138 -webkit-transform: rotate(359deg);
138 -webkit-transform: rotate(359deg);
139 transform: rotate(359deg);
139 transform: rotate(359deg);
140 }
140 }
141 }
141 }
142
142
143
143
144
144
145 .icon-no-margin::before {
145 .icon-no-margin::before {
146 margin: 0;
146 margin: 0;
147
147
148 }
148 }
149 // -- ICON CLASSES -- //
149 // -- ICON CLASSES -- //
150 // sorter = lambda s: '\n'.join(sorted(s.splitlines()))
150 // sorter = lambda s: '\n'.join(sorted(s.splitlines()))
151
151
152 .icon-delete:before { content: '\e800'; } /* '' */
152 .icon-delete:before { content: '\e800'; } /* '' */
153 .icon-ok:before { content: '\e801'; } /* '' */
153 .icon-ok:before { content: '\e801'; } /* '' */
154 .icon-comment:before { content: '\e802'; } /* '' */
154 .icon-comment:before { content: '\e802'; } /* '' */
155 .icon-bookmark:before { content: '\e803'; } /* '' */
155 .icon-bookmark:before { content: '\e803'; } /* '' */
156 .icon-branch:before { content: '\e804'; } /* '' */
156 .icon-branch:before { content: '\e804'; } /* '' */
157 .icon-tag:before { content: '\e805'; } /* '' */
157 .icon-tag:before { content: '\e805'; } /* '' */
158 .icon-lock:before { content: '\e806'; } /* '' */
158 .icon-lock:before { content: '\e806'; } /* '' */
159 .icon-unlock:before { content: '\e807'; } /* '' */
159 .icon-unlock:before { content: '\e807'; } /* '' */
160 .icon-feed:before { content: '\e808'; } /* '' */
160 .icon-feed:before { content: '\e808'; } /* '' */
161 .icon-left:before { content: '\e809'; } /* '' */
161 .icon-left:before { content: '\e809'; } /* '' */
162 .icon-right:before { content: '\e80a'; } /* '' */
162 .icon-right:before { content: '\e80a'; } /* '' */
163 .icon-down:before { content: '\e80b'; } /* '' */
163 .icon-down:before { content: '\e80b'; } /* '' */
164 .icon-folder:before { content: '\e80c'; } /* '' */
164 .icon-folder:before { content: '\e80c'; } /* '' */
165 .icon-folder-open:before { content: '\e80d'; } /* '' */
165 .icon-folder-open:before { content: '\e80d'; } /* '' */
166 .icon-trash-empty:before { content: '\e80e'; } /* '' */
166 .icon-trash-empty:before { content: '\e80e'; } /* '' */
167 .icon-group:before { content: '\e80f'; } /* '' */
167 .icon-group:before { content: '\e80f'; } /* '' */
168 .icon-remove:before { content: '\e810'; } /* '' */
168 .icon-remove:before { content: '\e810'; } /* '' */
169 .icon-fork:before { content: '\e811'; } /* '' */
169 .icon-fork:before { content: '\e811'; } /* '' */
170 .icon-more:before { content: '\e812'; } /* '' */
170 .icon-more:before { content: '\e812'; } /* '' */
171 .icon-options:before { content: '\e812'; } /* '' */
171 .icon-search:before { content: '\e813'; } /* '' */
172 .icon-search:before { content: '\e813'; } /* '' */
172 .icon-scissors:before { content: '\e814'; } /* '' */
173 .icon-scissors:before { content: '\e814'; } /* '' */
173 .icon-download:before { content: '\e815'; } /* '' */
174 .icon-download:before { content: '\e815'; } /* '' */
174 .icon-doc:before { content: '\e816'; } /* '' */
175 .icon-doc:before { content: '\e816'; } /* '' */
175 .icon-cog:before { content: '\e817'; } /* '' */
176 .icon-cog:before { content: '\e817'; } /* '' */
176 .icon-cog-alt:before { content: '\e818'; } /* '' */
177 .icon-cog-alt:before { content: '\e818'; } /* '' */
177 .icon-eye:before { content: '\e819'; } /* '' */
178 .icon-eye:before { content: '\e819'; } /* '' */
178 .icon-eye-off:before { content: '\e81a'; } /* '' */
179 .icon-eye-off:before { content: '\e81a'; } /* '' */
179 .icon-cancel-circled2:before { content: '\e81b'; } /* '' */
180 .icon-cancel-circled2:before { content: '\e81b'; } /* '' */
180 .icon-cancel-circled:before { content: '\e81c'; } /* '' */
181 .icon-cancel-circled:before { content: '\e81c'; } /* '' */
181 .icon-plus:before { content: '\e81d'; } /* '' */
182 .icon-plus:before { content: '\e81d'; } /* '' */
182 .icon-plus-circled:before { content: '\e81e'; } /* '' */
183 .icon-plus-circled:before { content: '\e81e'; } /* '' */
183 .icon-minus-circled:before { content: '\e81f'; } /* '' */
184 .icon-minus-circled:before { content: '\e81f'; } /* '' */
184 .icon-minus:before { content: '\e820'; } /* '' */
185 .icon-minus:before { content: '\e820'; } /* '' */
185 .icon-info-circled:before { content: '\e821'; } /* '' */
186 .icon-info-circled:before { content: '\e821'; } /* '' */
186 .icon-upload:before { content: '\e822'; } /* '' */
187 .icon-upload:before { content: '\e822'; } /* '' */
187 .icon-home:before { content: '\e823'; } /* '' */
188 .icon-home:before { content: '\e823'; } /* '' */
188 .icon-flag-filled:before { content: '\e824'; } /* '' */
189 .icon-flag-filled:before { content: '\e824'; } /* '' */
189 .icon-git:before { content: '\e82a'; } /* '' */
190 .icon-git:before { content: '\e82a'; } /* '' */
190 .icon-hg:before { content: '\e82d'; } /* '' */
191 .icon-hg:before { content: '\e82d'; } /* '' */
191 .icon-svn:before { content: '\e82e'; } /* '' */
192 .icon-svn:before { content: '\e82e'; } /* '' */
192 .icon-comment-add:before { content: '\e82f'; } /* '' */
193 .icon-comment-add:before { content: '\e82f'; } /* '' */
193 .icon-comment-toggle:before { content: '\e830'; } /* '' */
194 .icon-comment-toggle:before { content: '\e830'; } /* '' */
194 .icon-rhodecode:before { content: '\e831'; } /* '' */
195 .icon-rhodecode:before { content: '\e831'; } /* '' */
195 .icon-up:before { content: '\e832'; } /* '' */
196 .icon-up:before { content: '\e832'; } /* '' */
196 .icon-merge:before { content: '\e833'; } /* '' */
197 .icon-merge:before { content: '\e833'; } /* '' */
197 .icon-spin-alt:before { content: '\e834'; } /* '' */
198 .icon-spin-alt:before { content: '\e834'; } /* '' */
198 .icon-spin:before { content: '\e838'; } /* '' */
199 .icon-spin:before { content: '\e838'; } /* '' */
199 .icon-docs:before { content: '\f0c5'; } /* '' */
200 .icon-docs:before { content: '\f0c5'; } /* '' */
200 .icon-menu:before { content: '\f0c9'; } /* '' */
201 .icon-menu:before { content: '\f0c9'; } /* '' */
201 .icon-sort:before { content: '\f0dc'; } /* '' */
202 .icon-sort:before { content: '\f0dc'; } /* '' */
202 .icon-paste:before { content: '\f0ea'; } /* '' */
203 .icon-paste:before { content: '\f0ea'; } /* '' */
203 .icon-doc-text:before { content: '\f0f6'; } /* '' */
204 .icon-doc-text:before { content: '\f0f6'; } /* '' */
204 .icon-plus-squared:before { content: '\f0fe'; } /* '' */
205 .icon-plus-squared:before { content: '\f0fe'; } /* '' */
205 .icon-angle-left:before { content: '\f104'; } /* '' */
206 .icon-angle-left:before { content: '\f104'; } /* '' */
206 .icon-angle-right:before { content: '\f105'; } /* '' */
207 .icon-angle-right:before { content: '\f105'; } /* '' */
207 .icon-angle-up:before { content: '\f106'; } /* '' */
208 .icon-angle-up:before { content: '\f106'; } /* '' */
208 .icon-angle-down:before { content: '\f107'; } /* '' */
209 .icon-angle-down:before { content: '\f107'; } /* '' */
209 .icon-circle-empty:before { content: '\f10c'; } /* '' */
210 .icon-circle-empty:before { content: '\f10c'; } /* '' */
210 .icon-circle:before { content: '\f111'; } /* '' */
211 .icon-circle:before { content: '\f111'; } /* '' */
211 .icon-folder-empty:before { content: '\f114'; } /* '' */
212 .icon-folder-empty:before { content: '\f114'; } /* '' */
212 .icon-folder-open-empty:before { content: '\f115'; } /* '' */
213 .icon-folder-open-empty:before { content: '\f115'; } /* '' */
213 .icon-code:before { content: '\f121'; } /* '' */
214 .icon-code:before { content: '\f121'; } /* '' */
214 .icon-info:before { content: '\f129'; } /* '' */
215 .icon-info:before { content: '\f129'; } /* '' */
215 .icon-minus-squared:before { content: '\f146'; } /* '' */
216 .icon-minus-squared:before { content: '\f146'; } /* '' */
216 .icon-minus-squared-alt:before { content: '\f147'; } /* '' */
217 .icon-minus-squared-alt:before { content: '\f147'; } /* '' */
217 .icon-doc-inv:before { content: '\f15b'; } /* '' */
218 .icon-doc-inv:before { content: '\f15b'; } /* '' */
218 .icon-doc-text-inv:before { content: '\f15c'; } /* '' */
219 .icon-doc-text-inv:before { content: '\f15c'; } /* '' */
219 .icon-plus-squared-alt:before { content: '\f196'; } /* '' */
220 .icon-plus-squared-alt:before { content: '\f196'; } /* '' */
220 .icon-file-code:before { content: '\f1c9'; } /* '' */
221 .icon-file-code:before { content: '\f1c9'; } /* '' */
221 .icon-history:before { content: '\f1da'; } /* '' */
222 .icon-history:before { content: '\f1da'; } /* '' */
222 .icon-circle-thin:before { content: '\f1db'; } /* '' */
223 .icon-circle-thin:before { content: '\f1db'; } /* '' */
223 .icon-sliders:before { content: '\f1de'; } /* '' */
224 .icon-sliders:before { content: '\f1de'; } /* '' */
224 .icon-trash:before { content: '\f1f8'; } /* '' */
225 .icon-trash:before { content: '\f1f8'; } /* '' */
225
226
226
227
227 // MERGED ICONS BASED ON CURRENT ONES
228 // MERGED ICONS BASED ON CURRENT ONES
228 .icon-repo-group:before { &:extend(.icon-folder-open:before); }
229 .icon-repo-group:before { &:extend(.icon-folder-open:before); }
229 .icon-repo-private:before { &:extend(.icon-lock:before); }
230 .icon-repo-private:before { &:extend(.icon-lock:before); }
230 .icon-repo-lock:before { &:extend(.icon-lock:before); }
231 .icon-repo-lock:before { &:extend(.icon-lock:before); }
231 .icon-unlock-alt:before { &:extend(.icon-unlock:before); }
232 .icon-unlock-alt:before { &:extend(.icon-unlock:before); }
232 .icon-repo-unlock:before { &:extend(.icon-unlock:before); }
233 .icon-repo-unlock:before { &:extend(.icon-unlock:before); }
233 .icon-repo-public:before { &:extend(.icon-unlock:before); }
234 .icon-repo-public:before { &:extend(.icon-unlock:before); }
234 .icon-rss-sign:before { &:extend(.icon-feed:before); }
235 .icon-rss-sign:before { &:extend(.icon-feed:before); }
235 .icon-code-fork:before { &:extend(.icon-fork:before); }
236 .icon-code-fork:before { &:extend(.icon-fork:before); }
236 .icon-arrow_up:before { &:extend(.icon-up:before); }
237 .icon-arrow_up:before { &:extend(.icon-up:before); }
237 .icon-file:before { &:extend(.icon-file-code:before); }
238 .icon-file:before { &:extend(.icon-file-code:before); }
238 .icon-file-text:before { &:extend(.icon-file-code:before); }
239 .icon-file-text:before { &:extend(.icon-file-code:before); }
239 .icon-directory:before { &:extend(.icon-folder:before); }
240 .icon-directory:before { &:extend(.icon-folder:before); }
240 .icon-more-linked:before { &:extend(.icon-more:before); }
241 .icon-more-linked:before { &:extend(.icon-more:before); }
241 .icon-clipboard:before { &:extend(.icon-docs:before); }
242 .icon-clipboard:before { &:extend(.icon-docs:before); }
242 .icon-copy:before { &:extend(.icon-docs:before); }
243 .icon-copy:before { &:extend(.icon-docs:before); }
243 .icon-true:before { &:extend(.icon-ok:before); }
244 .icon-true:before { &:extend(.icon-ok:before); }
244 .icon-false:before { &:extend(.icon-delete:before); }
245 .icon-false:before { &:extend(.icon-delete:before); }
245 .icon-expand-linked:before { &:extend(.icon-down:before); }
246 .icon-expand-linked:before { &:extend(.icon-down:before); }
246 .icon-pr-merge-fail:before { &:extend(.icon-delete:before); }
247 .icon-pr-merge-fail:before { &:extend(.icon-delete:before); }
247 .icon-wide-mode:before { &:extend(.icon-sort:before); }
248 .icon-wide-mode:before { &:extend(.icon-sort:before); }
248 .icon-flag-filled-red:before { &:extend(.icon-flag-filled:before); }
249 .icon-flag-filled-red:before { &:extend(.icon-flag-filled:before); }
249 .icon-user-group-alt:before { &:extend(.icon-group:before); }
250 .icon-user-group-alt:before { &:extend(.icon-group:before); }
250
251
251 // TRANSFORM
252 // TRANSFORM
252 .icon-merge:before {transform: rotate(180deg);}
253 .icon-merge:before {transform: rotate(180deg);}
253 .icon-wide-mode:before {transform: rotate(90deg);}
254 .icon-wide-mode:before {transform: rotate(90deg);}
255 .icon-options:before {transform: rotate(90deg);}
254
256
255 // -- END ICON CLASSES -- //
257 // -- END ICON CLASSES -- //
256
258
257
259
258 //--- ICONS STYLING ------------------//
260 //--- ICONS STYLING ------------------//
259
261
260 .icon-git { color: @color4 !important; }
262 .icon-git { color: @color4 !important; }
261 .icon-hg { color: @color8 !important; }
263 .icon-hg { color: @color8 !important; }
262 .icon-svn { color: @color1 !important; }
264 .icon-svn { color: @color1 !important; }
263 .icon-git-inv { color: @color4 !important; }
265 .icon-git-inv { color: @color4 !important; }
264 .icon-hg-inv { color: @color8 !important; }
266 .icon-hg-inv { color: @color8 !important; }
265 .icon-svn-inv { color: @color1 !important; }
267 .icon-svn-inv { color: @color1 !important; }
266 .icon-repo-lock { color: #FF0000; }
268 .icon-repo-lock { color: #FF0000; }
267 .icon-repo-unlock { color: #FF0000; }
269 .icon-repo-unlock { color: #FF0000; }
268 .icon-false { color: @grey5 }
270 .icon-false { color: @grey5 }
269 .icon-expand-linked { cursor: pointer; color: @grey3; font-size: 14px }
271 .icon-expand-linked { cursor: pointer; color: @grey3; font-size: 14px }
270 .icon-more-linked { cursor: pointer; color: @grey3 }
272 .icon-more-linked { cursor: pointer; color: @grey3 }
271 .icon-flag-filled-red { color: @color5 !important; }
273 .icon-flag-filled-red { color: @color5 !important; }
272 .icon-filled-red { color: @color5 !important; }
274 .icon-filled-red { color: @color5 !important; }
273
275
274 .repo-switcher-dropdown .select2-result-label {
276 .repo-switcher-dropdown .select2-result-label {
275 .icon-git:before {
277 .icon-git:before {
276 &:extend(.icon-git-transparent:before);
278 &:extend(.icon-git-transparent:before);
277 }
279 }
278 .icon-hg:before {
280 .icon-hg:before {
279 &:extend(.icon-hg-transparent:before);
281 &:extend(.icon-hg-transparent:before);
280 color: @alert4;
282 color: @alert4;
281 }
283 }
282 .icon-svn:before {
284 .icon-svn:before {
283 &:extend(.icon-svn-transparent:before);
285 &:extend(.icon-svn-transparent:before);
284 }
286 }
285 }
287 }
286
288
287 .icon-user-group:before {
289 .icon-user-group:before {
288 &:extend(.icon-group:before);
290 &:extend(.icon-group:before);
289 margin: 0;
291 margin: 0;
290 font-size: 16px;
292 font-size: 16px;
291 }
293 }
@@ -1,137 +1,142 b''
1 // Global keyboard bindings
1 // Global keyboard bindings
2
2
3 function setRCMouseBindings(repoName, repoLandingRev) {
3 function setRCMouseBindings(repoName, repoLandingRev) {
4
4
5 /** custom callback for supressing mousetrap from firing */
5 /** custom callback for supressing mousetrap from firing */
6 Mousetrap.stopCallback = function(e, element) {
6 Mousetrap.stopCallback = function(e, element) {
7 // if the element has the class "mousetrap" then no need to stop
7 // if the element has the class "mousetrap" then no need to stop
8 if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
8 if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
9 return false;
9 return false;
10 }
10 }
11
11
12 // stop for input, select, and textarea
12 // stop for input, select, and textarea
13 return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
13 return element.tagName == 'INPUT' || element.tagName == 'SELECT' || element.tagName == 'TEXTAREA' || element.isContentEditable;
14 };
14 };
15
15
16 // general help "?"
16 // general help "?"
17 Mousetrap.bind(['?'], function(e) {
17 Mousetrap.bind(['?'], function(e) {
18 $('#help_kb').modal({});
18 $('#help_kb').modal({});
19 });
19 });
20
20
21 // / open the quick filter
21 // / open the quick filter
22 Mousetrap.bind(['/'], function(e) {
22 Mousetrap.bind(['/'], function(e) {
23 $('#main_filter').get(0).focus();
23 $('#main_filter').get(0).focus();
24
24
25 // return false to prevent default browser behavior
25 // return false to prevent default browser behavior
26 // and stop event from bubbling
26 // and stop event from bubbling
27 return false;
27 return false;
28 });
28 });
29
29
30 // ctrl/command+b, show the the main bar
30 // ctrl/command+b, show the the main bar
31 Mousetrap.bind(['command+b', 'ctrl+b'], function(e) {
31 Mousetrap.bind(['command+b', 'ctrl+b'], function(e) {
32 var $headerInner = $('#header-inner'),
32 var $headerInner = $('#header-inner'),
33 $content = $('#content');
33 $content = $('#content');
34 if ($headerInner.hasClass('hover') && $content.hasClass('hover')) {
34 if ($headerInner.hasClass('hover') && $content.hasClass('hover')) {
35 $headerInner.removeClass('hover');
35 $headerInner.removeClass('hover');
36 $content.removeClass('hover');
36 $content.removeClass('hover');
37 } else {
37 } else {
38 $headerInner.addClass('hover');
38 $headerInner.addClass('hover');
39 $content.addClass('hover');
39 $content.addClass('hover');
40 }
40 }
41 return false;
41 return false;
42 });
42 });
43
43
44 // general nav g + action
44 // general nav g + action
45 Mousetrap.bind(['g h'], function(e) {
45 Mousetrap.bind(['g h'], function(e) {
46 window.location = pyroutes.url('home');
46 window.location = pyroutes.url('home');
47 });
47 });
48 Mousetrap.bind(['g g'], function(e) {
48 Mousetrap.bind(['g g'], function(e) {
49 window.location = pyroutes.url('gists_show', {'private': 1});
49 window.location = pyroutes.url('gists_show', {'private': 1});
50 });
50 });
51 Mousetrap.bind(['g G'], function(e) {
51 Mousetrap.bind(['g G'], function(e) {
52 window.location = pyroutes.url('gists_show', {'public': 1});
52 window.location = pyroutes.url('gists_show', {'public': 1});
53 });
53 });
54
54
55 Mousetrap.bind(['g 0'], function(e) {
55 Mousetrap.bind(['g 0'], function(e) {
56 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 0});
56 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 0});
57 });
57 });
58 Mousetrap.bind(['g 1'], function(e) {
58 Mousetrap.bind(['g 1'], function(e) {
59 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 1});
59 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 1});
60 });
60 });
61 Mousetrap.bind(['g 2'], function(e) {
61 Mousetrap.bind(['g 2'], function(e) {
62 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 2});
62 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 2});
63 });
63 });
64 Mousetrap.bind(['g 3'], function(e) {
64 Mousetrap.bind(['g 3'], function(e) {
65 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 3});
65 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 3});
66 });
66 });
67 Mousetrap.bind(['g 4'], function(e) {
67 Mousetrap.bind(['g 4'], function(e) {
68 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 4});
68 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 4});
69 });
69 });
70 Mousetrap.bind(['g 5'], function(e) {
70 Mousetrap.bind(['g 5'], function(e) {
71 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 5});
71 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 5});
72 });
72 });
73 Mousetrap.bind(['g 6'], function(e) {
73 Mousetrap.bind(['g 6'], function(e) {
74 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 6});
74 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 6});
75 });
75 });
76 Mousetrap.bind(['g 7'], function(e) {
76 Mousetrap.bind(['g 7'], function(e) {
77 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 7});
77 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 7});
78 });
78 });
79 Mousetrap.bind(['g 8'], function(e) {
79 Mousetrap.bind(['g 8'], function(e) {
80 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 8});
80 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 8});
81 });
81 });
82 Mousetrap.bind(['g 9'], function(e) {
82 Mousetrap.bind(['g 9'], function(e) {
83 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 9});
83 window.location = pyroutes.url('my_account_goto_bookmark', {'bookmark_id': 9});
84 });
84 });
85
85
86 Mousetrap.bind(['n g'], function(e) {
86 Mousetrap.bind(['n g'], function(e) {
87 window.location = pyroutes.url('gists_new');
87 window.location = pyroutes.url('gists_new');
88 });
88 });
89 Mousetrap.bind(['n r'], function(e) {
89 Mousetrap.bind(['n r'], function(e) {
90 window.location = pyroutes.url('repo_new');
90 window.location = pyroutes.url('repo_new');
91 });
91 });
92
92
93 if (repoName && repoName !== '') {
93 if (repoName && repoName !== '') {
94 // nav in repo context
94 // nav in repo context
95 Mousetrap.bind(['g s'], function(e) {
95 Mousetrap.bind(['g s'], function(e) {
96 window.location = pyroutes.url(
96 window.location = pyroutes.url(
97 'repo_summary', {'repo_name': repoName});
97 'repo_summary', {'repo_name': repoName});
98 });
98 });
99 Mousetrap.bind(['g c'], function(e) {
99 Mousetrap.bind(['g c'], function(e) {
100 window.location = pyroutes.url(
100 window.location = pyroutes.url(
101 'repo_commits', {'repo_name': repoName});
101 'repo_commits', {'repo_name': repoName});
102 });
102 });
103 Mousetrap.bind(['g F'], function(e) {
103 Mousetrap.bind(['g F'], function(e) {
104 window.location = pyroutes.url(
104 window.location = pyroutes.url(
105 'repo_files',
105 'repo_files',
106 {
106 {
107 'repo_name': repoName,
107 'repo_name': repoName,
108 'commit_id': repoLandingRev,
108 'commit_id': repoLandingRev,
109 'f_path': '',
109 'f_path': '',
110 'search': '1'
110 'search': '1'
111 });
111 });
112 });
112 });
113 Mousetrap.bind(['g f'], function(e) {
113 Mousetrap.bind(['g f'], function(e) {
114 window.location = pyroutes.url(
114 window.location = pyroutes.url(
115 'repo_files',
115 'repo_files',
116 {
116 {
117 'repo_name': repoName,
117 'repo_name': repoName,
118 'commit_id': repoLandingRev,
118 'commit_id': repoLandingRev,
119 'f_path': ''
119 'f_path': ''
120 });
120 });
121 });
121 });
122 Mousetrap.bind(['g p'], function(e) {
122 Mousetrap.bind(['g p'], function(e) {
123 window.location = pyroutes.url(
123 window.location = pyroutes.url(
124 'pullrequest_show_all', {'repo_name': repoName});
124 'pullrequest_show_all', {'repo_name': repoName});
125 });
125 });
126 Mousetrap.bind(['g o'], function(e) {
126 Mousetrap.bind(['g o'], function(e) {
127 window.location = pyroutes.url(
127 window.location = pyroutes.url(
128 'edit_repo', {'repo_name': repoName});
128 'edit_repo', {'repo_name': repoName});
129 });
129 });
130 Mousetrap.bind(['g O'], function(e) {
130 Mousetrap.bind(['g O'], function(e) {
131 window.location = pyroutes.url(
131 window.location = pyroutes.url(
132 'edit_repo_perms', {'repo_name': repoName});
132 'edit_repo_perms', {'repo_name': repoName});
133 });
133 });
134 Mousetrap.bind(['t s'], function(e) {
135 if (window.toggleSidebar !== undefined) {
136 window.toggleSidebar();
137 }
138 });
134 }
139 }
135 }
140 }
136
141
137 setRCMouseBindings(templateContext.repo_name, templateContext.repo_landing_commit);
142 setRCMouseBindings(templateContext.repo_name, templateContext.repo_landing_commit);
@@ -1,38 +1,109 b''
1 // # Copyright (C) 2010-2020 RhodeCode GmbH
1 // # Copyright (C) 2010-2020 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19 /**
19 /**
20 * QUICK REPO MENU, used on repositories to show shortcuts to files, history
20 * QUICK REPO MENU, used on repositories to show shortcuts to files, history
21 * etc.
21 * etc.
22 */
22 */
23
23
24 var quick_repo_menu = function() {
24 var quick_repo_menu = function() {
25 var hide_quick_repo_menus = function() {
25 var hide_quick_repo_menus = function() {
26 $('.menu_items_container').hide();
26 $('.menu_items_container').hide();
27 $('.active_quick_repo').removeClass('active_quick_repo');
27 $('.active_quick_repo').removeClass('active_quick_repo');
28 };
28 };
29 $('.quick_repo_menu').hover(function() {
29 $('.quick_repo_menu').hover(function() {
30 hide_quick_repo_menus();
30 hide_quick_repo_menus();
31 if (!$(this).hasClass('active_quick_repo')) {
31 if (!$(this).hasClass('active_quick_repo')) {
32 $('.menu_items_container', this).removeClass("hidden").show();
32 $('.menu_items_container', this).removeClass("hidden").show();
33 $(this).addClass('active_quick_repo');
33 $(this).addClass('active_quick_repo');
34 }
34 }
35 }, function() {
35 }, function() {
36 hide_quick_repo_menus();
36 hide_quick_repo_menus();
37 });
37 });
38 }; No newline at end of file
38 };
39
40
41 window.toggleElement = function (elem, target) {
42 var $elem = $(elem);
43 var $target = $(target);
44
45 if ($target.is(':visible') || $target.length === 0) {
46 $target.hide();
47 $elem.html($elem.data('toggleOn'))
48 } else {
49 $target.show();
50 $elem.html($elem.data('toggleOff'))
51 }
52
53 return false
54 }
55
56 var marginExpVal = '300' // needs a sync with `.right-sidebar.right-sidebar-expanded` value
57 var marginColVal = '40' // needs a sync with `.right-sidebar.right-sidebar-collapsed` value
58
59 var marginExpanded = {'margin': '0 {0}px 0 0'.format(marginExpVal)};
60 var marginCollapsed = {'margin': '0 {0}px 0 0'.format(marginColVal)};
61
62 var updateStickyHeader = function () {
63 if (window.updateSticky !== undefined) {
64 // potentially our comments change the active window size, so we
65 // notify sticky elements
66 updateSticky()
67 }
68 }
69
70 var expandSidebar = function () {
71 var $sideBar = $('.right-sidebar');
72 $('.outerwrapper').css(marginExpanded);
73 $('.sidebar-toggle a').html('<i class="icon-right" style="margin-right: -10px"></i><i class="icon-right"></i>');
74 $('.right-sidebar-collapsed-state').hide();
75 $('.right-sidebar-expanded-state').show();
76 $('.branding').addClass('display-none');
77 $sideBar.addClass('right-sidebar-expanded')
78 $sideBar.removeClass('right-sidebar-collapsed')
79 }
80
81 var collapseSidebar = function () {
82 var $sideBar = $('.right-sidebar');
83 $('.outerwrapper').css(marginCollapsed);
84 $('.sidebar-toggle a').html('<i class="icon-left" style="margin-right: -10px"></i><i class="icon-left"></i>');
85 $('.right-sidebar-collapsed-state').show();
86 $('.right-sidebar-expanded-state').hide();
87 $('.branding').removeClass('display-none');
88 $sideBar.removeClass('right-sidebar-expanded')
89 $sideBar.addClass('right-sidebar-collapsed')
90 }
91
92 window.toggleSidebar = function () {
93 var $sideBar = $('.right-sidebar');
94
95 if ($sideBar.hasClass('right-sidebar-expanded')) {
96 // expanded -> collapsed transition
97 collapseSidebar();
98 var sidebarState = 'collapsed';
99
100 } else {
101 // collapsed -> expanded
102 expandSidebar();
103 var sidebarState = 'expanded';
104 }
105
106 // update our other sticky header in same context
107 updateStickyHeader();
108 storeUserSessionAttr('rc_user_session_attr.sidebarState', sidebarState);
109 }
@@ -1,675 +1,890 b''
1 // # Copyright (C) 2010-2020 RhodeCode GmbH
1 // # Copyright (C) 2010-2020 RhodeCode GmbH
2 // #
2 // #
3 // # This program is free software: you can redistribute it and/or modify
3 // # This program is free software: you can redistribute it and/or modify
4 // # it under the terms of the GNU Affero General Public License, version 3
4 // # it under the terms of the GNU Affero General Public License, version 3
5 // # (only), as published by the Free Software Foundation.
5 // # (only), as published by the Free Software Foundation.
6 // #
6 // #
7 // # This program is distributed in the hope that it will be useful,
7 // # This program is distributed in the hope that it will be useful,
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 // # GNU General Public License for more details.
10 // # GNU General Public License for more details.
11 // #
11 // #
12 // # You should have received a copy of the GNU Affero General Public License
12 // # You should have received a copy of the GNU Affero General Public License
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 // #
14 // #
15 // # This program is dual-licensed. If you wish to learn more about the
15 // # This program is dual-licensed. If you wish to learn more about the
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18
18
19
19
20 var prButtonLockChecks = {
20 var prButtonLockChecks = {
21 'compare': false,
21 'compare': false,
22 'reviewers': false
22 'reviewers': false
23 };
23 };
24
24
25 /**
25 /**
26 * lock button until all checks and loads are made. E.g reviewer calculation
26 * lock button until all checks and loads are made. E.g reviewer calculation
27 * should prevent from submitting a PR
27 * should prevent from submitting a PR
28 * @param lockEnabled
28 * @param lockEnabled
29 * @param msg
29 * @param msg
30 * @param scope
30 * @param scope
31 */
31 */
32 var prButtonLock = function(lockEnabled, msg, scope) {
32 var prButtonLock = function(lockEnabled, msg, scope) {
33 scope = scope || 'all';
33 scope = scope || 'all';
34 if (scope == 'all'){
34 if (scope == 'all'){
35 prButtonLockChecks['compare'] = !lockEnabled;
35 prButtonLockChecks['compare'] = !lockEnabled;
36 prButtonLockChecks['reviewers'] = !lockEnabled;
36 prButtonLockChecks['reviewers'] = !lockEnabled;
37 } else if (scope == 'compare') {
37 } else if (scope == 'compare') {
38 prButtonLockChecks['compare'] = !lockEnabled;
38 prButtonLockChecks['compare'] = !lockEnabled;
39 } else if (scope == 'reviewers'){
39 } else if (scope == 'reviewers'){
40 prButtonLockChecks['reviewers'] = !lockEnabled;
40 prButtonLockChecks['reviewers'] = !lockEnabled;
41 }
41 }
42 var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
42 var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
43 if (lockEnabled) {
43 if (lockEnabled) {
44 $('#pr_submit').attr('disabled', 'disabled');
44 $('#pr_submit').attr('disabled', 'disabled');
45 }
45 }
46 else if (checksMeet) {
46 else if (checksMeet) {
47 $('#pr_submit').removeAttr('disabled');
47 $('#pr_submit').removeAttr('disabled');
48 }
48 }
49
49
50 if (msg) {
50 if (msg) {
51 $('#pr_open_message').html(msg);
51 $('#pr_open_message').html(msg);
52 }
52 }
53 };
53 };
54
54
55
55
56 /**
56 /**
57 Generate Title and Description for a PullRequest.
57 Generate Title and Description for a PullRequest.
58 In case of 1 commits, the title and description is that one commit
58 In case of 1 commits, the title and description is that one commit
59 in case of multiple commits, we iterate on them with max N number of commits,
59 in case of multiple commits, we iterate on them with max N number of commits,
60 and build description in a form
60 and build description in a form
61 - commitN
61 - commitN
62 - commitN+1
62 - commitN+1
63 ...
63 ...
64
64
65 Title is then constructed from branch names, or other references,
65 Title is then constructed from branch names, or other references,
66 replacing '-' and '_' into spaces
66 replacing '-' and '_' into spaces
67
67
68 * @param sourceRef
68 * @param sourceRef
69 * @param elements
69 * @param elements
70 * @param limit
70 * @param limit
71 * @returns {*[]}
71 * @returns {*[]}
72 */
72 */
73 var getTitleAndDescription = function(sourceRefType, sourceRef, elements, limit) {
73 var getTitleAndDescription = function(sourceRefType, sourceRef, elements, limit) {
74 var title = '';
74 var title = '';
75 var desc = '';
75 var desc = '';
76
76
77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
78 var rawMessage = value['message'];
78 var rawMessage = value['message'];
79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
80 });
80 });
81 // only 1 commit, use commit message as title
81 // only 1 commit, use commit message as title
82 if (elements.length === 1) {
82 if (elements.length === 1) {
83 var rawMessage = elements[0]['message'];
83 var rawMessage = elements[0]['message'];
84 title = rawMessage.split('\n')[0];
84 title = rawMessage.split('\n')[0];
85 }
85 }
86 else {
86 else {
87 // use reference name
87 // use reference name
88 var normalizedRef = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter()
88 var normalizedRef = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter()
89 var refType = sourceRefType;
89 var refType = sourceRefType;
90 title = 'Changes from {0}: {1}'.format(refType, normalizedRef);
90 title = 'Changes from {0}: {1}'.format(refType, normalizedRef);
91 }
91 }
92
92
93 return [title, desc]
93 return [title, desc]
94 };
94 };
95
95
96
96
97 ReviewersController = function () {
97 ReviewersController = function () {
98 var self = this;
98 var self = this;
99 this.$reviewRulesContainer = $('#review_rules');
99 this.$reviewRulesContainer = $('#review_rules');
100 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
100 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
101 this.$userRule = $('.pr-user-rule-container');
101 this.$userRule = $('.pr-user-rule-container');
102 this.forbidReviewUsers = undefined;
102 this.forbidReviewUsers = undefined;
103 this.$reviewMembers = $('#review_members');
103 this.$reviewMembers = $('#review_members');
104 this.currentRequest = null;
104 this.currentRequest = null;
105 this.diffData = null;
105 this.diffData = null;
106 this.enabledRules = [];
106 this.enabledRules = [];
107
107
108 //dummy handler, we might register our own later
108 //dummy handler, we might register our own later
109 this.diffDataHandler = function(data){};
109 this.diffDataHandler = function(data){};
110
110
111 this.defaultForbidReviewUsers = function () {
111 this.defaultForbidReviewUsers = function () {
112 return [
112 return [
113 {
113 {
114 'username': 'default',
114 'username': 'default',
115 'user_id': templateContext.default_user.user_id
115 'user_id': templateContext.default_user.user_id
116 }
116 }
117 ];
117 ];
118 };
118 };
119
119
120 this.hideReviewRules = function () {
120 this.hideReviewRules = function () {
121 self.$reviewRulesContainer.hide();
121 self.$reviewRulesContainer.hide();
122 $(self.$userRule.selector).hide();
122 $(self.$userRule.selector).hide();
123 };
123 };
124
124
125 this.showReviewRules = function () {
125 this.showReviewRules = function () {
126 self.$reviewRulesContainer.show();
126 self.$reviewRulesContainer.show();
127 $(self.$userRule.selector).show();
127 $(self.$userRule.selector).show();
128 };
128 };
129
129
130 this.addRule = function (ruleText) {
130 this.addRule = function (ruleText) {
131 self.showReviewRules();
131 self.showReviewRules();
132 self.enabledRules.push(ruleText);
132 self.enabledRules.push(ruleText);
133 return '<div>- {0}</div>'.format(ruleText)
133 return '<div>- {0}</div>'.format(ruleText)
134 };
134 };
135
135
136 this.loadReviewRules = function (data) {
136 this.loadReviewRules = function (data) {
137 self.diffData = data;
137 self.diffData = data;
138
138
139 // reset forbidden Users
139 // reset forbidden Users
140 this.forbidReviewUsers = self.defaultForbidReviewUsers();
140 this.forbidReviewUsers = self.defaultForbidReviewUsers();
141
141
142 // reset state of review rules
142 // reset state of review rules
143 self.$rulesList.html('');
143 self.$rulesList.html('');
144
144
145 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
145 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
146 // default rule, case for older repo that don't have any rules stored
146 // default rule, case for older repo that don't have any rules stored
147 self.$rulesList.append(
147 self.$rulesList.append(
148 self.addRule(
148 self.addRule(
149 _gettext('All reviewers must vote.'))
149 _gettext('All reviewers must vote.'))
150 );
150 );
151 return self.forbidReviewUsers
151 return self.forbidReviewUsers
152 }
152 }
153
153
154 if (data.rules.voting !== undefined) {
154 if (data.rules.voting !== undefined) {
155 if (data.rules.voting < 0) {
155 if (data.rules.voting < 0) {
156 self.$rulesList.append(
156 self.$rulesList.append(
157 self.addRule(
157 self.addRule(
158 _gettext('All individual reviewers must vote.'))
158 _gettext('All individual reviewers must vote.'))
159 )
159 )
160 } else if (data.rules.voting === 1) {
160 } else if (data.rules.voting === 1) {
161 self.$rulesList.append(
161 self.$rulesList.append(
162 self.addRule(
162 self.addRule(
163 _gettext('At least {0} reviewer must vote.').format(data.rules.voting))
163 _gettext('At least {0} reviewer must vote.').format(data.rules.voting))
164 )
164 )
165
165
166 } else {
166 } else {
167 self.$rulesList.append(
167 self.$rulesList.append(
168 self.addRule(
168 self.addRule(
169 _gettext('At least {0} reviewers must vote.').format(data.rules.voting))
169 _gettext('At least {0} reviewers must vote.').format(data.rules.voting))
170 )
170 )
171 }
171 }
172 }
172 }
173
173
174 if (data.rules.voting_groups !== undefined) {
174 if (data.rules.voting_groups !== undefined) {
175 $.each(data.rules.voting_groups, function (index, rule_data) {
175 $.each(data.rules.voting_groups, function (index, rule_data) {
176 self.$rulesList.append(
176 self.$rulesList.append(
177 self.addRule(rule_data.text)
177 self.addRule(rule_data.text)
178 )
178 )
179 });
179 });
180 }
180 }
181
181
182 if (data.rules.use_code_authors_for_review) {
182 if (data.rules.use_code_authors_for_review) {
183 self.$rulesList.append(
183 self.$rulesList.append(
184 self.addRule(
184 self.addRule(
185 _gettext('Reviewers picked from source code changes.'))
185 _gettext('Reviewers picked from source code changes.'))
186 )
186 )
187 }
187 }
188
188
189 if (data.rules.forbid_adding_reviewers) {
189 if (data.rules.forbid_adding_reviewers) {
190 $('#add_reviewer_input').remove();
190 $('#add_reviewer_input').remove();
191 self.$rulesList.append(
191 self.$rulesList.append(
192 self.addRule(
192 self.addRule(
193 _gettext('Adding new reviewers is forbidden.'))
193 _gettext('Adding new reviewers is forbidden.'))
194 )
194 )
195 }
195 }
196
196
197 if (data.rules.forbid_author_to_review) {
197 if (data.rules.forbid_author_to_review) {
198 self.forbidReviewUsers.push(data.rules_data.pr_author);
198 self.forbidReviewUsers.push(data.rules_data.pr_author);
199 self.$rulesList.append(
199 self.$rulesList.append(
200 self.addRule(
200 self.addRule(
201 _gettext('Author is not allowed to be a reviewer.'))
201 _gettext('Author is not allowed to be a reviewer.'))
202 )
202 )
203 }
203 }
204
204
205 if (data.rules.forbid_commit_author_to_review) {
205 if (data.rules.forbid_commit_author_to_review) {
206
206
207 if (data.rules_data.forbidden_users) {
207 if (data.rules_data.forbidden_users) {
208 $.each(data.rules_data.forbidden_users, function (index, member_data) {
208 $.each(data.rules_data.forbidden_users, function (index, member_data) {
209 self.forbidReviewUsers.push(member_data)
209 self.forbidReviewUsers.push(member_data)
210 });
210 });
211
211
212 }
212 }
213
213
214 self.$rulesList.append(
214 self.$rulesList.append(
215 self.addRule(
215 self.addRule(
216 _gettext('Commit Authors are not allowed to be a reviewer.'))
216 _gettext('Commit Authors are not allowed to be a reviewer.'))
217 )
217 )
218 }
218 }
219
219
220 // we don't have any rules set, so we inform users about it
220 // we don't have any rules set, so we inform users about it
221 if (self.enabledRules.length === 0) {
221 if (self.enabledRules.length === 0) {
222 self.addRule(
222 self.addRule(
223 _gettext('No review rules set.'))
223 _gettext('No review rules set.'))
224 }
224 }
225
225
226 return self.forbidReviewUsers
226 return self.forbidReviewUsers
227 };
227 };
228
228
229 this.loadDefaultReviewers = function (sourceRepo, sourceRef, targetRepo, targetRef) {
229 this.loadDefaultReviewers = function (sourceRepo, sourceRef, targetRepo, targetRef) {
230
230
231 if (self.currentRequest) {
231 if (self.currentRequest) {
232 // make sure we cleanup old running requests before triggering this again
232 // make sure we cleanup old running requests before triggering this again
233 self.currentRequest.abort();
233 self.currentRequest.abort();
234 }
234 }
235
235
236 $('.calculate-reviewers').show();
236 $('.calculate-reviewers').show();
237 // reset reviewer members
237 // reset reviewer members
238 self.$reviewMembers.empty();
238 self.$reviewMembers.empty();
239
239
240 prButtonLock(true, null, 'reviewers');
240 prButtonLock(true, null, 'reviewers');
241 $('#user').hide(); // hide user autocomplete before load
241 $('#user').hide(); // hide user autocomplete before load
242
242
243 // lock PR button, so we cannot send PR before it's calculated
243 // lock PR button, so we cannot send PR before it's calculated
244 prButtonLock(true, _gettext('Loading diff ...'), 'compare');
244 prButtonLock(true, _gettext('Loading diff ...'), 'compare');
245
245
246 if (sourceRef.length !== 3 || targetRef.length !== 3) {
246 if (sourceRef.length !== 3 || targetRef.length !== 3) {
247 // don't load defaults in case we're missing some refs...
247 // don't load defaults in case we're missing some refs...
248 $('.calculate-reviewers').hide();
248 $('.calculate-reviewers').hide();
249 return
249 return
250 }
250 }
251
251
252 var url = pyroutes.url('repo_default_reviewers_data',
252 var url = pyroutes.url('repo_default_reviewers_data',
253 {
253 {
254 'repo_name': templateContext.repo_name,
254 'repo_name': templateContext.repo_name,
255 'source_repo': sourceRepo,
255 'source_repo': sourceRepo,
256 'source_ref': sourceRef[2],
256 'source_ref': sourceRef[2],
257 'target_repo': targetRepo,
257 'target_repo': targetRepo,
258 'target_ref': targetRef[2]
258 'target_ref': targetRef[2]
259 });
259 });
260
260
261 self.currentRequest = $.ajax({
261 self.currentRequest = $.ajax({
262 url: url,
262 url: url,
263 headers: {'X-PARTIAL-XHR': true},
263 headers: {'X-PARTIAL-XHR': true},
264 type: 'GET',
264 type: 'GET',
265 success: function (data) {
265 success: function (data) {
266
266
267 self.currentRequest = null;
267 self.currentRequest = null;
268
268
269 // review rules
269 // review rules
270 self.loadReviewRules(data);
270 self.loadReviewRules(data);
271 self.handleDiffData(data["diff_info"]);
271 self.handleDiffData(data["diff_info"]);
272
272
273 for (var i = 0; i < data.reviewers.length; i++) {
273 for (var i = 0; i < data.reviewers.length; i++) {
274 var reviewer = data.reviewers[i];
274 var reviewer = data.reviewers[i];
275 self.addReviewMember(reviewer, reviewer.reasons, reviewer.mandatory);
275 self.addReviewMember(reviewer, reviewer.reasons, reviewer.mandatory);
276 }
276 }
277 $('.calculate-reviewers').hide();
277 $('.calculate-reviewers').hide();
278 prButtonLock(false, null, 'reviewers');
278 prButtonLock(false, null, 'reviewers');
279 $('#user').show(); // show user autocomplete after load
279 $('#user').show(); // show user autocomplete after load
280
280
281 var commitElements = data["diff_info"]['commits'];
281 var commitElements = data["diff_info"]['commits'];
282
282 if (commitElements.length === 0) {
283 if (commitElements.length === 0) {
283 prButtonLock(true, _gettext('no commits'), 'all');
284 var noCommitsMsg = '<span class="alert-text-warning">{0}</span>'.format(
285 _gettext('There are no commits to merge.'));
286 prButtonLock(true, noCommitsMsg, 'all');
284
287
285 } else {
288 } else {
286 // un-lock PR button, so we cannot send PR before it's calculated
289 // un-lock PR button, so we cannot send PR before it's calculated
287 prButtonLock(false, null, 'compare');
290 prButtonLock(false, null, 'compare');
288 }
291 }
289
292
290 },
293 },
291 error: function (jqXHR, textStatus, errorThrown) {
294 error: function (jqXHR, textStatus, errorThrown) {
292 var prefix = "Loading diff and reviewers failed\n"
295 var prefix = "Loading diff and reviewers failed\n"
293 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
296 var message = formatErrorMessage(jqXHR, textStatus, errorThrown, prefix);
294 ajaxErrorSwal(message);
297 ajaxErrorSwal(message);
295 }
298 }
296 });
299 });
297
300
298 };
301 };
299
302
300 // check those, refactor
303 // check those, refactor
301 this.removeReviewMember = function (reviewer_id, mark_delete) {
304 this.removeReviewMember = function (reviewer_id, mark_delete) {
302 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
305 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
303
306
304 if (typeof (mark_delete) === undefined) {
307 if (typeof (mark_delete) === undefined) {
305 mark_delete = false;
308 mark_delete = false;
306 }
309 }
307
310
308 if (mark_delete === true) {
311 if (mark_delete === true) {
309 if (reviewer) {
312 if (reviewer) {
310 // now delete the input
313 // now delete the input
311 $('#reviewer_{0} input'.format(reviewer_id)).remove();
314 $('#reviewer_{0} input'.format(reviewer_id)).remove();
312 // mark as to-delete
315 // mark as to-delete
313 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
316 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
314 obj.addClass('to-delete');
317 obj.addClass('to-delete');
315 obj.css({"text-decoration": "line-through", "opacity": 0.5});
318 obj.css({"text-decoration": "line-through", "opacity": 0.5});
316 }
319 }
317 } else {
320 } else {
318 $('#reviewer_{0}'.format(reviewer_id)).remove();
321 $('#reviewer_{0}'.format(reviewer_id)).remove();
319 }
322 }
320 };
323 };
321
324
322 this.reviewMemberEntry = function () {
325 this.reviewMemberEntry = function () {
323
326
324 };
327 };
325
328
326 this.addReviewMember = function (reviewer_obj, reasons, mandatory) {
329 this.addReviewMember = function (reviewer_obj, reasons, mandatory) {
327 var members = self.$reviewMembers.get(0);
328 var id = reviewer_obj.user_id;
330 var id = reviewer_obj.user_id;
329 var username = reviewer_obj.username;
331 var username = reviewer_obj.username;
330
332
331 var reasons = reasons || [];
333 var reasons = reasons || [];
332 var mandatory = mandatory || false;
334 var mandatory = mandatory || false;
333
335
334 // register IDS to check if we don't have this ID already in
336 // register IDS to check if we don't have this ID already in
335 var currentIds = [];
337 var currentIds = [];
336 var _els = self.$reviewMembers.find('li').toArray();
338
337 for (el in _els) {
339 $.each(self.$reviewMembers.find('.reviewer_entry'), function (index, value) {
338 currentIds.push(_els[el].id)
340 currentIds.push($(value).data('reviewerUserId'))
339 }
341 })
340
342
341 var userAllowedReview = function (userId) {
343 var userAllowedReview = function (userId) {
342 var allowed = true;
344 var allowed = true;
343 $.each(self.forbidReviewUsers, function (index, member_data) {
345 $.each(self.forbidReviewUsers, function (index, member_data) {
344 if (parseInt(userId) === member_data['user_id']) {
346 if (parseInt(userId) === member_data['user_id']) {
345 allowed = false;
347 allowed = false;
346 return false // breaks the loop
348 return false // breaks the loop
347 }
349 }
348 });
350 });
349 return allowed
351 return allowed
350 };
352 };
351
353
352 var userAllowed = userAllowedReview(id);
354 var userAllowed = userAllowedReview(id);
353 if (!userAllowed) {
355 if (!userAllowed) {
354 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
356 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
355 } else {
357 } else {
356 // only add if it's not there
358 // only add if it's not there
357 var alreadyReviewer = currentIds.indexOf('reviewer_' + id) != -1;
359 var alreadyReviewer = currentIds.indexOf(id) != -1;
358
360
359 if (alreadyReviewer) {
361 if (alreadyReviewer) {
360 alert(_gettext('User `{0}` already in reviewers').format(username));
362 alert(_gettext('User `{0}` already in reviewers').format(username));
361 } else {
363 } else {
362 members.innerHTML += renderTemplate('reviewMemberEntry', {
364 var reviewerEntry = renderTemplate('reviewMemberEntry', {
363 'member': reviewer_obj,
365 'member': reviewer_obj,
364 'mandatory': mandatory,
366 'mandatory': mandatory,
365 'reasons': reasons,
367 'reasons': reasons,
366 'allowed_to_update': true,
368 'allowed_to_update': true,
367 'review_status': 'not_reviewed',
369 'review_status': 'not_reviewed',
368 'review_status_label': _gettext('Not Reviewed'),
370 'review_status_label': _gettext('Not Reviewed'),
369 'user_group': reviewer_obj.user_group,
371 'user_group': reviewer_obj.user_group,
370 'create': true,
372 'create': true,
371 });
373 'rule_show': true,
374 })
375 $(self.$reviewMembers.selector).append(reviewerEntry);
372 tooltipActivate();
376 tooltipActivate();
373 }
377 }
374 }
378 }
375
379
376 };
380 };
377
381
378 this.updateReviewers = function (repo_name, pull_request_id) {
382 this.updateReviewers = function (repo_name, pull_request_id) {
379 var postData = $('#reviewers input').serialize();
383 var postData = $('#reviewers input').serialize();
380 _updatePullRequest(repo_name, pull_request_id, postData);
384 _updatePullRequest(repo_name, pull_request_id, postData);
381 };
385 };
382
386
383 this.handleDiffData = function (data) {
387 this.handleDiffData = function (data) {
384 self.diffDataHandler(data)
388 self.diffDataHandler(data)
385 }
389 }
386 };
390 };
387
391
388
392
389 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
393 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
390 var url = pyroutes.url(
394 var url = pyroutes.url(
391 'pullrequest_update',
395 'pullrequest_update',
392 {"repo_name": repo_name, "pull_request_id": pull_request_id});
396 {"repo_name": repo_name, "pull_request_id": pull_request_id});
393 if (typeof postData === 'string' ) {
397 if (typeof postData === 'string' ) {
394 postData += '&csrf_token=' + CSRF_TOKEN;
398 postData += '&csrf_token=' + CSRF_TOKEN;
395 } else {
399 } else {
396 postData.csrf_token = CSRF_TOKEN;
400 postData.csrf_token = CSRF_TOKEN;
397 }
401 }
398
402
399 var success = function(o) {
403 var success = function(o) {
400 var redirectUrl = o['redirect_url'];
404 var redirectUrl = o['redirect_url'];
401 if (redirectUrl !== undefined && redirectUrl !== null && redirectUrl !== '') {
405 if (redirectUrl !== undefined && redirectUrl !== null && redirectUrl !== '') {
402 window.location = redirectUrl;
406 window.location = redirectUrl;
403 } else {
407 } else {
404 window.location.reload();
408 window.location.reload();
405 }
409 }
406 };
410 };
407
411
408 ajaxPOST(url, postData, success);
412 ajaxPOST(url, postData, success);
409 };
413 };
410
414
411 /**
415 /**
412 * PULL REQUEST update commits
416 * PULL REQUEST update commits
413 */
417 */
414 var updateCommits = function(repo_name, pull_request_id, force) {
418 var updateCommits = function(repo_name, pull_request_id, force) {
415 var postData = {
419 var postData = {
416 'update_commits': true
420 'update_commits': true
417 };
421 };
418 if (force !== undefined && force === true) {
422 if (force !== undefined && force === true) {
419 postData['force_refresh'] = true
423 postData['force_refresh'] = true
420 }
424 }
421 _updatePullRequest(repo_name, pull_request_id, postData);
425 _updatePullRequest(repo_name, pull_request_id, postData);
422 };
426 };
423
427
424
428
425 /**
429 /**
426 * PULL REQUEST edit info
430 * PULL REQUEST edit info
427 */
431 */
428 var editPullRequest = function(repo_name, pull_request_id, title, description, renderer) {
432 var editPullRequest = function(repo_name, pull_request_id, title, description, renderer) {
429 var url = pyroutes.url(
433 var url = pyroutes.url(
430 'pullrequest_update',
434 'pullrequest_update',
431 {"repo_name": repo_name, "pull_request_id": pull_request_id});
435 {"repo_name": repo_name, "pull_request_id": pull_request_id});
432
436
433 var postData = {
437 var postData = {
434 'title': title,
438 'title': title,
435 'description': description,
439 'description': description,
436 'description_renderer': renderer,
440 'description_renderer': renderer,
437 'edit_pull_request': true,
441 'edit_pull_request': true,
438 'csrf_token': CSRF_TOKEN
442 'csrf_token': CSRF_TOKEN
439 };
443 };
440 var success = function(o) {
444 var success = function(o) {
441 window.location.reload();
445 window.location.reload();
442 };
446 };
443 ajaxPOST(url, postData, success);
447 ajaxPOST(url, postData, success);
444 };
448 };
445
449
446
450
447 /**
451 /**
448 * Reviewer autocomplete
452 * Reviewer autocomplete
449 */
453 */
450 var ReviewerAutoComplete = function(inputId) {
454 var ReviewerAutoComplete = function(inputId) {
451 $(inputId).autocomplete({
455 $(inputId).autocomplete({
452 serviceUrl: pyroutes.url('user_autocomplete_data'),
456 serviceUrl: pyroutes.url('user_autocomplete_data'),
453 minChars:2,
457 minChars:2,
454 maxHeight:400,
458 maxHeight:400,
455 deferRequestBy: 300, //miliseconds
459 deferRequestBy: 300, //miliseconds
456 showNoSuggestionNotice: true,
460 showNoSuggestionNotice: true,
457 tabDisabled: true,
461 tabDisabled: true,
458 autoSelectFirst: true,
462 autoSelectFirst: true,
459 params: { user_id: templateContext.rhodecode_user.user_id, user_groups:true, user_groups_expand:true, skip_default_user:true },
463 params: { user_id: templateContext.rhodecode_user.user_id, user_groups:true, user_groups_expand:true, skip_default_user:true },
460 formatResult: autocompleteFormatResult,
464 formatResult: autocompleteFormatResult,
461 lookupFilter: autocompleteFilterResult,
465 lookupFilter: autocompleteFilterResult,
462 onSelect: function(element, data) {
466 onSelect: function(element, data) {
463 var mandatory = false;
467 var mandatory = false;
464 var reasons = [_gettext('added manually by "{0}"').format(templateContext.rhodecode_user.username)];
468 var reasons = [_gettext('added manually by "{0}"').format(templateContext.rhodecode_user.username)];
465
469
466 // add whole user groups
470 // add whole user groups
467 if (data.value_type == 'user_group') {
471 if (data.value_type == 'user_group') {
468 reasons.push(_gettext('member of "{0}"').format(data.value_display));
472 reasons.push(_gettext('member of "{0}"').format(data.value_display));
469
473
470 $.each(data.members, function(index, member_data) {
474 $.each(data.members, function(index, member_data) {
471 var reviewer = member_data;
475 var reviewer = member_data;
472 reviewer['user_id'] = member_data['id'];
476 reviewer['user_id'] = member_data['id'];
473 reviewer['gravatar_link'] = member_data['icon_link'];
477 reviewer['gravatar_link'] = member_data['icon_link'];
474 reviewer['user_link'] = member_data['profile_link'];
478 reviewer['user_link'] = member_data['profile_link'];
475 reviewer['rules'] = [];
479 reviewer['rules'] = [];
476 reviewersController.addReviewMember(reviewer, reasons, mandatory);
480 reviewersController.addReviewMember(reviewer, reasons, mandatory);
477 })
481 })
478 }
482 }
479 // add single user
483 // add single user
480 else {
484 else {
481 var reviewer = data;
485 var reviewer = data;
482 reviewer['user_id'] = data['id'];
486 reviewer['user_id'] = data['id'];
483 reviewer['gravatar_link'] = data['icon_link'];
487 reviewer['gravatar_link'] = data['icon_link'];
484 reviewer['user_link'] = data['profile_link'];
488 reviewer['user_link'] = data['profile_link'];
485 reviewer['rules'] = [];
489 reviewer['rules'] = [];
486 reviewersController.addReviewMember(reviewer, reasons, mandatory);
490 reviewersController.addReviewMember(reviewer, reasons, mandatory);
487 }
491 }
488
492
489 $(inputId).val('');
493 $(inputId).val('');
490 }
494 }
491 });
495 });
492 };
496 };
493
497
494
498
495 VersionController = function () {
499 window.VersionController = function () {
496 var self = this;
500 var self = this;
497 this.$verSource = $('input[name=ver_source]');
501 this.$verSource = $('input[name=ver_source]');
498 this.$verTarget = $('input[name=ver_target]');
502 this.$verTarget = $('input[name=ver_target]');
499 this.$showVersionDiff = $('#show-version-diff');
503 this.$showVersionDiff = $('#show-version-diff');
500
504
501 this.adjustRadioSelectors = function (curNode) {
505 this.adjustRadioSelectors = function (curNode) {
502 var getVal = function (item) {
506 var getVal = function (item) {
503 if (item == 'latest') {
507 if (item == 'latest') {
504 return Number.MAX_SAFE_INTEGER
508 return Number.MAX_SAFE_INTEGER
505 }
509 }
506 else {
510 else {
507 return parseInt(item)
511 return parseInt(item)
508 }
512 }
509 };
513 };
510
514
511 var curVal = getVal($(curNode).val());
515 var curVal = getVal($(curNode).val());
512 var cleared = false;
516 var cleared = false;
513
517
514 $.each(self.$verSource, function (index, value) {
518 $.each(self.$verSource, function (index, value) {
515 var elVal = getVal($(value).val());
519 var elVal = getVal($(value).val());
516
520
517 if (elVal > curVal) {
521 if (elVal > curVal) {
518 if ($(value).is(':checked')) {
522 if ($(value).is(':checked')) {
519 cleared = true;
523 cleared = true;
520 }
524 }
521 $(value).attr('disabled', 'disabled');
525 $(value).attr('disabled', 'disabled');
522 $(value).removeAttr('checked');
526 $(value).removeAttr('checked');
523 $(value).css({'opacity': 0.1});
527 $(value).css({'opacity': 0.1});
524 }
528 }
525 else {
529 else {
526 $(value).css({'opacity': 1});
530 $(value).css({'opacity': 1});
527 $(value).removeAttr('disabled');
531 $(value).removeAttr('disabled');
528 }
532 }
529 });
533 });
530
534
531 if (cleared) {
535 if (cleared) {
532 // if we unchecked an active, set the next one to same loc.
536 // if we unchecked an active, set the next one to same loc.
533 $(this.$verSource).filter('[value={0}]'.format(
537 $(this.$verSource).filter('[value={0}]'.format(
534 curVal)).attr('checked', 'checked');
538 curVal)).attr('checked', 'checked');
535 }
539 }
536
540
537 self.setLockAction(false,
541 self.setLockAction(false,
538 $(curNode).data('verPos'),
542 $(curNode).data('verPos'),
539 $(this.$verSource).filter(':checked').data('verPos')
543 $(this.$verSource).filter(':checked').data('verPos')
540 );
544 );
541 };
545 };
542
546
543
547
544 this.attachVersionListener = function () {
548 this.attachVersionListener = function () {
545 self.$verTarget.change(function (e) {
549 self.$verTarget.change(function (e) {
546 self.adjustRadioSelectors(this)
550 self.adjustRadioSelectors(this)
547 });
551 });
548 self.$verSource.change(function (e) {
552 self.$verSource.change(function (e) {
549 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
553 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
550 });
554 });
551 };
555 };
552
556
553 this.init = function () {
557 this.init = function () {
554
558
555 var curNode = self.$verTarget.filter(':checked');
559 var curNode = self.$verTarget.filter(':checked');
556 self.adjustRadioSelectors(curNode);
560 self.adjustRadioSelectors(curNode);
557 self.setLockAction(true);
561 self.setLockAction(true);
558 self.attachVersionListener();
562 self.attachVersionListener();
559
563
560 };
564 };
561
565
562 this.setLockAction = function (state, selectedVersion, otherVersion) {
566 this.setLockAction = function (state, selectedVersion, otherVersion) {
563 var $showVersionDiff = this.$showVersionDiff;
567 var $showVersionDiff = this.$showVersionDiff;
564
568
565 if (state) {
569 if (state) {
566 $showVersionDiff.attr('disabled', 'disabled');
570 $showVersionDiff.attr('disabled', 'disabled');
567 $showVersionDiff.addClass('disabled');
571 $showVersionDiff.addClass('disabled');
568 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
572 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
569 }
573 }
570 else {
574 else {
571 $showVersionDiff.removeAttr('disabled');
575 $showVersionDiff.removeAttr('disabled');
572 $showVersionDiff.removeClass('disabled');
576 $showVersionDiff.removeClass('disabled');
573
577
574 if (selectedVersion == otherVersion) {
578 if (selectedVersion == otherVersion) {
575 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
579 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
576 } else {
580 } else {
577 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
581 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
578 }
582 }
579 }
583 }
580
584
581 };
585 };
582
586
583 this.showVersionDiff = function () {
587 this.showVersionDiff = function () {
584 var target = self.$verTarget.filter(':checked');
588 var target = self.$verTarget.filter(':checked');
585 var source = self.$verSource.filter(':checked');
589 var source = self.$verSource.filter(':checked');
586
590
587 if (target.val() && source.val()) {
591 if (target.val() && source.val()) {
588 var params = {
592 var params = {
589 'pull_request_id': templateContext.pull_request_data.pull_request_id,
593 'pull_request_id': templateContext.pull_request_data.pull_request_id,
590 'repo_name': templateContext.repo_name,
594 'repo_name': templateContext.repo_name,
591 'version': target.val(),
595 'version': target.val(),
592 'from_version': source.val()
596 'from_version': source.val()
593 };
597 };
594 window.location = pyroutes.url('pullrequest_show', params)
598 window.location = pyroutes.url('pullrequest_show', params)
595 }
599 }
596
600
597 return false;
601 return false;
598 };
602 };
599
603
600 this.toggleVersionView = function (elem) {
604 this.toggleVersionView = function (elem) {
601
605
602 if (this.$showVersionDiff.is(':visible')) {
606 if (this.$showVersionDiff.is(':visible')) {
603 $('.version-pr').hide();
607 $('.version-pr').hide();
604 this.$showVersionDiff.hide();
608 this.$showVersionDiff.hide();
605 $(elem).html($(elem).data('toggleOn'))
609 $(elem).html($(elem).data('toggleOn'))
606 } else {
610 } else {
607 $('.version-pr').show();
611 $('.version-pr').show();
608 this.$showVersionDiff.show();
612 this.$showVersionDiff.show();
609 $(elem).html($(elem).data('toggleOff'))
613 $(elem).html($(elem).data('toggleOff'))
610 }
614 }
611
615
612 return false
616 return false
613 };
617 };
614
618
615 this.toggleElement = function (elem, target) {
616 var $elem = $(elem);
617 var $target = $(target);
618
619 if ($target.is(':visible') || $target.length === 0) {
620 $target.hide();
621 $elem.html($elem.data('toggleOn'))
622 } else {
623 $target.show();
624 $elem.html($elem.data('toggleOff'))
625 }
626
627 return false
628 }
629
630 };
619 };
631
620
632
621
633 UpdatePrController = function () {
622 window.UpdatePrController = function () {
634 var self = this;
623 var self = this;
635 this.$updateCommits = $('#update_commits');
624 this.$updateCommits = $('#update_commits');
636 this.$updateCommitsSwitcher = $('#update_commits_switcher');
625 this.$updateCommitsSwitcher = $('#update_commits_switcher');
637
626
638 this.lockUpdateButton = function (label) {
627 this.lockUpdateButton = function (label) {
639 self.$updateCommits.attr('disabled', 'disabled');
628 self.$updateCommits.attr('disabled', 'disabled');
640 self.$updateCommitsSwitcher.attr('disabled', 'disabled');
629 self.$updateCommitsSwitcher.attr('disabled', 'disabled');
641
630
642 self.$updateCommits.addClass('disabled');
631 self.$updateCommits.addClass('disabled');
643 self.$updateCommitsSwitcher.addClass('disabled');
632 self.$updateCommitsSwitcher.addClass('disabled');
644
633
645 self.$updateCommits.removeClass('btn-primary');
634 self.$updateCommits.removeClass('btn-primary');
646 self.$updateCommitsSwitcher.removeClass('btn-primary');
635 self.$updateCommitsSwitcher.removeClass('btn-primary');
647
636
648 self.$updateCommits.text(_gettext(label));
637 self.$updateCommits.text(_gettext(label));
649 };
638 };
650
639
651 this.isUpdateLocked = function () {
640 this.isUpdateLocked = function () {
652 return self.$updateCommits.attr('disabled') !== undefined;
641 return self.$updateCommits.attr('disabled') !== undefined;
653 };
642 };
654
643
655 this.updateCommits = function (curNode) {
644 this.updateCommits = function (curNode) {
656 if (self.isUpdateLocked()) {
645 if (self.isUpdateLocked()) {
657 return
646 return
658 }
647 }
659 self.lockUpdateButton(_gettext('Updating...'));
648 self.lockUpdateButton(_gettext('Updating...'));
660 updateCommits(
649 updateCommits(
661 templateContext.repo_name,
650 templateContext.repo_name,
662 templateContext.pull_request_data.pull_request_id);
651 templateContext.pull_request_data.pull_request_id);
663 };
652 };
664
653
665 this.forceUpdateCommits = function () {
654 this.forceUpdateCommits = function () {
666 if (self.isUpdateLocked()) {
655 if (self.isUpdateLocked()) {
667 return
656 return
668 }
657 }
669 self.lockUpdateButton(_gettext('Force updating...'));
658 self.lockUpdateButton(_gettext('Force updating...'));
670 var force = true;
659 var force = true;
671 updateCommits(
660 updateCommits(
672 templateContext.repo_name,
661 templateContext.repo_name,
673 templateContext.pull_request_data.pull_request_id, force);
662 templateContext.pull_request_data.pull_request_id, force);
674 };
663 };
675 }; No newline at end of file
664 };
665
666 /**
667 * Reviewer display panel
668 */
669 window.ReviewersPanel = {
670 editButton: null,
671 closeButton: null,
672 addButton: null,
673 removeButtons: null,
674 reviewRules: null,
675 setReviewers: null,
676
677 setSelectors: function () {
678 var self = this;
679 self.editButton = $('#open_edit_reviewers');
680 self.closeButton =$('#close_edit_reviewers');
681 self.addButton = $('#add_reviewer');
682 self.removeButtons = $('.reviewer_member_remove,.reviewer_member_mandatory_remove');
683 },
684
685 init: function (reviewRules, setReviewers) {
686 var self = this;
687 self.setSelectors();
688
689 this.reviewRules = reviewRules;
690 this.setReviewers = setReviewers;
691
692 this.editButton.on('click', function (e) {
693 self.edit();
694 });
695 this.closeButton.on('click', function (e) {
696 self.close();
697 self.renderReviewers();
698 });
699
700 self.renderReviewers();
701
702 },
703
704 renderReviewers: function () {
705
706 $('#review_members').html('')
707 $.each(this.setReviewers.reviewers, function (key, val) {
708 var member = val;
709
710 var entry = renderTemplate('reviewMemberEntry', {
711 'member': member,
712 'mandatory': member.mandatory,
713 'reasons': member.reasons,
714 'allowed_to_update': member.allowed_to_update,
715 'review_status': member.review_status,
716 'review_status_label': member.review_status_label,
717 'user_group': member.user_group,
718 'create': false
719 });
720
721 $('#review_members').append(entry)
722 });
723 tooltipActivate();
724
725 },
726
727 edit: function (event) {
728 this.editButton.hide();
729 this.closeButton.show();
730 this.addButton.show();
731 $(this.removeButtons.selector).css('visibility', 'visible');
732 // review rules
733 reviewersController.loadReviewRules(this.reviewRules);
734 },
735
736 close: function (event) {
737 this.editButton.show();
738 this.closeButton.hide();
739 this.addButton.hide();
740 $(this.removeButtons.selector).css('visibility', 'hidden');
741 // hide review rules
742 reviewersController.hideReviewRules()
743 }
744 };
745
746
747 /**
748 * OnLine presence using channelstream
749 */
750 window.ReviewerPresenceController = function (channel) {
751 var self = this;
752 this.channel = channel;
753 this.users = {};
754
755 this.storeUsers = function (users) {
756 self.users = {}
757 $.each(users, function (index, value) {
758 var userId = value.state.id;
759 self.users[userId] = value.state;
760 })
761 }
762
763 this.render = function () {
764 $.each($('.reviewer_entry'), function (index, value) {
765 var userData = $(value).data();
766 if (self.users[userData.reviewerUserId] !== undefined) {
767 $(value).find('.presence-state').show();
768 } else {
769 $(value).find('.presence-state').hide();
770 }
771 })
772 };
773
774 this.handlePresence = function (data) {
775 if (data.type == 'presence' && data.channel === self.channel) {
776 this.storeUsers(data.users);
777 this.render()
778 }
779 };
780
781 this.handleChannelUpdate = function (data) {
782 if (data.channel === this.channel) {
783 this.storeUsers(data.state.users);
784 this.render()
785 }
786
787 };
788
789 /* subscribe to the current presence */
790 $.Topic('/connection_controller/presence').subscribe(this.handlePresence.bind(this));
791 /* subscribe to updates e.g connect/disconnect */
792 $.Topic('/connection_controller/channel_update').subscribe(this.handleChannelUpdate.bind(this));
793
794 };
795
796 window.refreshComments = function (version) {
797 version = version || templateContext.pull_request_data.pull_request_version || '';
798
799 // Pull request case
800 if (templateContext.pull_request_data.pull_request_id !== null) {
801 var params = {
802 'pull_request_id': templateContext.pull_request_data.pull_request_id,
803 'repo_name': templateContext.repo_name,
804 'version': version,
805 };
806 var loadUrl = pyroutes.url('pullrequest_comments', params);
807 } // commit case
808 else {
809 return
810 }
811
812 var currentIDs = []
813 $.each($('.comment'), function (idx, element) {
814 currentIDs.push($(element).data('commentId'));
815 });
816 var data = {"comments[]": currentIDs};
817
818 var $targetElem = $('.comments-content-table');
819 $targetElem.css('opacity', 0.3);
820 $targetElem.load(
821 loadUrl, data, function (responseText, textStatus, jqXHR) {
822 if (jqXHR.status !== 200) {
823 return false;
824 }
825 var $counterElem = $('#comments-count');
826 var newCount = $(responseText).data('counter');
827 if (newCount !== undefined) {
828 var callback = function () {
829 $counterElem.animate({'opacity': 1.00}, 200)
830 $counterElem.html(newCount);
831 };
832 $counterElem.animate({'opacity': 0.15}, 200, callback);
833 }
834
835 $targetElem.css('opacity', 1);
836 tooltipActivate();
837 }
838 );
839 }
840
841 window.refreshTODOs = function (version) {
842 version = version || templateContext.pull_request_data.pull_request_version || '';
843 // Pull request case
844 if (templateContext.pull_request_data.pull_request_id !== null) {
845 var params = {
846 'pull_request_id': templateContext.pull_request_data.pull_request_id,
847 'repo_name': templateContext.repo_name,
848 'version': version,
849 };
850 var loadUrl = pyroutes.url('pullrequest_comments', params);
851 } // commit case
852 else {
853 return
854 }
855
856 var currentIDs = []
857 $.each($('.comment'), function (idx, element) {
858 currentIDs.push($(element).data('commentId'));
859 });
860
861 var data = {"comments[]": currentIDs};
862 var $targetElem = $('.todos-content-table');
863 $targetElem.css('opacity', 0.3);
864 $targetElem.load(
865 loadUrl, data, function (responseText, textStatus, jqXHR) {
866 if (jqXHR.status !== 200) {
867 return false;
868 }
869 var $counterElem = $('#todos-count')
870 var newCount = $(responseText).data('counter');
871 if (newCount !== undefined) {
872 var callback = function () {
873 $counterElem.animate({'opacity': 1.00}, 200)
874 $counterElem.html(newCount);
875 };
876 $counterElem.animate({'opacity': 0.15}, 200, callback);
877 }
878
879 $targetElem.css('opacity', 1);
880 tooltipActivate();
881 }
882 );
883 }
884
885 window.refreshAllComments = function (version) {
886 version = version || templateContext.pull_request_data.pull_request_version || '';
887
888 refreshComments(version);
889 refreshTODOs(version);
890 };
@@ -1,1223 +1,1254 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%!
3 <%!
4 from rhodecode.lib import html_filters
4 from rhodecode.lib import html_filters
5 %>
5 %>
6
6
7 <%inherit file="root.mako"/>
7 <%inherit file="root.mako"/>
8
8
9 <%include file="/ejs_templates/templates.html"/>
9 <%include file="/ejs_templates/templates.html"/>
10
10
11 <div class="outerwrapper">
11 <div class="outerwrapper">
12 <!-- HEADER -->
12 <!-- HEADER -->
13 <div class="header">
13 <div class="header">
14 <div id="header-inner" class="wrapper">
14 <div id="header-inner" class="wrapper">
15 <div id="logo">
15 <div id="logo">
16 <div class="logo-wrapper">
16 <div class="logo-wrapper">
17 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
17 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-60x60.png')}" alt="RhodeCode"/></a>
18 </div>
18 </div>
19 % if c.rhodecode_name:
19 % if c.rhodecode_name:
20 <div class="branding">
20 <div class="branding">
21 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
21 <a href="${h.route_path('home')}">${h.branding(c.rhodecode_name)}</a>
22 </div>
22 </div>
23 % endif
23 % endif
24 </div>
24 </div>
25 <!-- MENU BAR NAV -->
25 <!-- MENU BAR NAV -->
26 ${self.menu_bar_nav()}
26 ${self.menu_bar_nav()}
27 <!-- END MENU BAR NAV -->
27 <!-- END MENU BAR NAV -->
28 </div>
28 </div>
29 </div>
29 </div>
30 ${self.menu_bar_subnav()}
30 ${self.menu_bar_subnav()}
31 <!-- END HEADER -->
31 <!-- END HEADER -->
32
32
33 <!-- CONTENT -->
33 <!-- CONTENT -->
34 <div id="content" class="wrapper">
34 <div id="content" class="wrapper">
35
35
36 <rhodecode-toast id="notifications"></rhodecode-toast>
36 <rhodecode-toast id="notifications"></rhodecode-toast>
37
37
38 <div class="main">
38 <div class="main">
39 ${next.main()}
39 ${next.main()}
40 </div>
40 </div>
41
41
42 </div>
42 </div>
43 <!-- END CONTENT -->
43 <!-- END CONTENT -->
44
44
45 </div>
45 </div>
46
46
47 <!-- FOOTER -->
47 <!-- FOOTER -->
48 <div id="footer">
48 <div id="footer">
49 <div id="footer-inner" class="title wrapper">
49 <div id="footer-inner" class="title wrapper">
50 <div>
50 <div>
51 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
51 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
52
52
53 <p class="footer-link-right">
53 <p class="footer-link-right">
54 <a class="grey-link-action" href="${h.route_path('home', _query={'showrcid': 1})}">
54 <a class="grey-link-action" href="${h.route_path('home', _query={'showrcid': 1})}">
55 RhodeCode
55 RhodeCode
56 % if c.visual.show_version:
56 % if c.visual.show_version:
57 ${c.rhodecode_version}
57 ${c.rhodecode_version}
58 % endif
58 % endif
59 ${c.rhodecode_edition}
59 ${c.rhodecode_edition}
60 </a> |
60 </a> |
61
61
62 % if c.visual.rhodecode_support_url:
62 % if c.visual.rhodecode_support_url:
63 <a class="grey-link-action" href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a> |
63 <a class="grey-link-action" href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a> |
64 <a class="grey-link-action" href="https://docs.rhodecode.com" target="_blank">${_('Documentation')}</a>
64 <a class="grey-link-action" href="https://docs.rhodecode.com" target="_blank">${_('Documentation')}</a>
65 % endif
65 % endif
66
66
67 </p>
67 </p>
68
68
69 <p class="server-instance" style="display:${sid}">
69 <p class="server-instance" style="display:${sid}">
70 ## display hidden instance ID if specially defined
70 ## display hidden instance ID if specially defined
71 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
71 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
72 % if c.rhodecode_instanceid:
72 % if c.rhodecode_instanceid:
73 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
73 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
74 % endif
74 % endif
75 </p>
75 </p>
76 </div>
76 </div>
77 </div>
77 </div>
78 </div>
78 </div>
79
79
80 <!-- END FOOTER -->
80 <!-- END FOOTER -->
81
81
82 ### MAKO DEFS ###
82 ### MAKO DEFS ###
83
83
84 <%def name="menu_bar_subnav()">
84 <%def name="menu_bar_subnav()">
85 </%def>
85 </%def>
86
86
87 <%def name="breadcrumbs(class_='breadcrumbs')">
87 <%def name="breadcrumbs(class_='breadcrumbs')">
88 <div class="${class_}">
88 <div class="${class_}">
89 ${self.breadcrumbs_links()}
89 ${self.breadcrumbs_links()}
90 </div>
90 </div>
91 </%def>
91 </%def>
92
92
93 <%def name="admin_menu(active=None)">
93 <%def name="admin_menu(active=None)">
94
94
95 <div id="context-bar">
95 <div id="context-bar">
96 <div class="wrapper">
96 <div class="wrapper">
97 <div class="title">
97 <div class="title">
98 <div class="title-content">
98 <div class="title-content">
99 <div class="title-main">
99 <div class="title-main">
100 % if c.is_super_admin:
100 % if c.is_super_admin:
101 ${_('Super-admin Panel')}
101 ${_('Super-admin Panel')}
102 % else:
102 % else:
103 ${_('Delegated Admin Panel')}
103 ${_('Delegated Admin Panel')}
104 % endif
104 % endif
105 </div>
105 </div>
106 </div>
106 </div>
107 </div>
107 </div>
108
108
109 <ul id="context-pages" class="navigation horizontal-list">
109 <ul id="context-pages" class="navigation horizontal-list">
110
110
111 ## super-admin case
111 ## super-admin case
112 % if c.is_super_admin:
112 % if c.is_super_admin:
113 <li class="${h.is_active('audit_logs', active)}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
113 <li class="${h.is_active('audit_logs', active)}"><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
114 <li class="${h.is_active('repositories', active)}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
114 <li class="${h.is_active('repositories', active)}"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
115 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
115 <li class="${h.is_active('repository_groups', active)}"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
116 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
116 <li class="${h.is_active('users', active)}"><a href="${h.route_path('users')}">${_('Users')}</a></li>
117 <li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
117 <li class="${h.is_active('user_groups', active)}"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
118 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
118 <li class="${h.is_active('permissions', active)}"><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
119 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
119 <li class="${h.is_active('authentication', active)}"><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
120 <li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
120 <li class="${h.is_active('integrations', active)}"><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
121 <li class="${h.is_active('defaults', active)}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
121 <li class="${h.is_active('defaults', active)}"><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
122 <li class="${h.is_active('settings', active)}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
122 <li class="${h.is_active('settings', active)}"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
123
123
124 ## delegated admin
124 ## delegated admin
125 % elif c.is_delegated_admin:
125 % elif c.is_delegated_admin:
126 <%
126 <%
127 repositories=c.auth_user.repositories_admin or c.can_create_repo
127 repositories=c.auth_user.repositories_admin or c.can_create_repo
128 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
128 repository_groups=c.auth_user.repository_groups_admin or c.can_create_repo_group
129 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
129 user_groups=c.auth_user.user_groups_admin or c.can_create_user_group
130 %>
130 %>
131
131
132 %if repositories:
132 %if repositories:
133 <li class="${h.is_active('repositories', active)} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
133 <li class="${h.is_active('repositories', active)} local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
134 %endif
134 %endif
135 %if repository_groups:
135 %if repository_groups:
136 <li class="${h.is_active('repository_groups', active)} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
136 <li class="${h.is_active('repository_groups', active)} local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
137 %endif
137 %endif
138 %if user_groups:
138 %if user_groups:
139 <li class="${h.is_active('user_groups', active)} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
139 <li class="${h.is_active('user_groups', active)} local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
140 %endif
140 %endif
141 % endif
141 % endif
142 </ul>
142 </ul>
143
143
144 </div>
144 </div>
145 <div class="clear"></div>
145 <div class="clear"></div>
146 </div>
146 </div>
147 </%def>
147 </%def>
148
148
149 <%def name="dt_info_panel(elements)">
149 <%def name="dt_info_panel(elements)">
150 <dl class="dl-horizontal">
150 <dl class="dl-horizontal">
151 %for dt, dd, title, show_items in elements:
151 %for dt, dd, title, show_items in elements:
152 <dt>${dt}:</dt>
152 <dt>${dt}:</dt>
153 <dd title="${h.tooltip(title)}">
153 <dd title="${h.tooltip(title)}">
154 %if callable(dd):
154 %if callable(dd):
155 ## allow lazy evaluation of elements
155 ## allow lazy evaluation of elements
156 ${dd()}
156 ${dd()}
157 %else:
157 %else:
158 ${dd}
158 ${dd}
159 %endif
159 %endif
160 %if show_items:
160 %if show_items:
161 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
161 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
162 %endif
162 %endif
163 </dd>
163 </dd>
164
164
165 %if show_items:
165 %if show_items:
166 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
166 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
167 %for item in show_items:
167 %for item in show_items:
168 <dt></dt>
168 <dt></dt>
169 <dd>${item}</dd>
169 <dd>${item}</dd>
170 %endfor
170 %endfor
171 </div>
171 </div>
172 %endif
172 %endif
173
173
174 %endfor
174 %endfor
175 </dl>
175 </dl>
176 </%def>
176 </%def>
177
177
178 <%def name="tr_info_entry(element)">
178 <%def name="tr_info_entry(element)">
179 <% key, val, title, show_items = element %>
179 <% key, val, title, show_items = element %>
180
180
181 <tr>
181 <tr>
182 <td style="vertical-align: top">${key}</td>
182 <td style="vertical-align: top">${key}</td>
183 <td title="${h.tooltip(title)}">
183 <td title="${h.tooltip(title)}">
184 %if callable(val):
184 %if callable(val):
185 ## allow lazy evaluation of elements
185 ## allow lazy evaluation of elements
186 ${val()}
186 ${val()}
187 %else:
187 %else:
188 ${val}
188 ${val}
189 %endif
189 %endif
190 %if show_items:
190 %if show_items:
191 <div class="collapsable-content" data-toggle="item-${h.md5_safe(val)[:6]}-details" style="display: none">
191 <div class="collapsable-content" data-toggle="item-${h.md5_safe(val)[:6]}-details" style="display: none">
192 % for item in show_items:
192 % for item in show_items:
193 <dt></dt>
193 <dt></dt>
194 <dd>${item}</dd>
194 <dd>${item}</dd>
195 % endfor
195 % endfor
196 </div>
196 </div>
197 %endif
197 %endif
198 </td>
198 </td>
199 <td style="vertical-align: top">
199 <td style="vertical-align: top">
200 %if show_items:
200 %if show_items:
201 <span class="btn-collapse" data-toggle="item-${h.md5_safe(val)[:6]}-details">${_('Show More')} </span>
201 <span class="btn-collapse" data-toggle="item-${h.md5_safe(val)[:6]}-details">${_('Show More')} </span>
202 %endif
202 %endif
203 </td>
203 </td>
204 </tr>
204 </tr>
205
205
206 </%def>
206 </%def>
207
207
208 <%def name="gravatar(email, size=16, tooltip=False, tooltip_alt=None, user=None, extra_class=None)">
208 <%def name="gravatar(email, size=16, tooltip=False, tooltip_alt=None, user=None, extra_class=None)">
209 <%
209 <%
210 if size > 16:
210 if size > 16:
211 gravatar_class = ['gravatar','gravatar-large']
211 gravatar_class = ['gravatar','gravatar-large']
212 else:
212 else:
213 gravatar_class = ['gravatar']
213 gravatar_class = ['gravatar']
214
214
215 data_hovercard_url = ''
215 data_hovercard_url = ''
216 data_hovercard_alt = tooltip_alt.replace('<', '&lt;').replace('>', '&gt;') if tooltip_alt else ''
216 data_hovercard_alt = tooltip_alt.replace('<', '&lt;').replace('>', '&gt;') if tooltip_alt else ''
217
217
218 if tooltip:
218 if tooltip:
219 gravatar_class += ['tooltip-hovercard']
219 gravatar_class += ['tooltip-hovercard']
220 if extra_class:
220 if extra_class:
221 gravatar_class += extra_class
221 gravatar_class += extra_class
222 if tooltip and user:
222 if tooltip and user:
223 if user.username == h.DEFAULT_USER:
223 if user.username == h.DEFAULT_USER:
224 gravatar_class.pop(-1)
224 gravatar_class.pop(-1)
225 else:
225 else:
226 data_hovercard_url = request.route_path('hovercard_user', user_id=getattr(user, 'user_id', ''))
226 data_hovercard_url = request.route_path('hovercard_user', user_id=getattr(user, 'user_id', ''))
227 gravatar_class = ' '.join(gravatar_class)
227 gravatar_class = ' '.join(gravatar_class)
228
228
229 %>
229 %>
230 <%doc>
230 <%doc>
231 TODO: johbo: For now we serve double size images to make it smooth
231 TODO: johbo: For now we serve double size images to make it smooth
232 for retina. This is how it worked until now. Should be replaced
232 for retina. This is how it worked until now. Should be replaced
233 with a better solution at some point.
233 with a better solution at some point.
234 </%doc>
234 </%doc>
235
235
236 <img class="${gravatar_class}" height="${size}" width="${size}" data-hovercard-url="${data_hovercard_url}" data-hovercard-alt="${data_hovercard_alt}" src="${h.gravatar_url(email, size * 2)}" />
236 <img class="${gravatar_class}" height="${size}" width="${size}" data-hovercard-url="${data_hovercard_url}" data-hovercard-alt="${data_hovercard_alt}" src="${h.gravatar_url(email, size * 2)}" />
237 </%def>
237 </%def>
238
238
239
239
240 <%def name="gravatar_with_user(contact, size=16, show_disabled=False, tooltip=False, _class='rc-user')">
240 <%def name="gravatar_with_user(contact, size=16, show_disabled=False, tooltip=False, _class='rc-user')">
241 <%
241 <%
242 email = h.email_or_none(contact)
242 email = h.email_or_none(contact)
243 rc_user = h.discover_user(contact)
243 rc_user = h.discover_user(contact)
244 %>
244 %>
245
245
246 <div class="${_class}">
246 <div class="${_class}">
247 ${self.gravatar(email, size, tooltip=tooltip, tooltip_alt=contact, user=rc_user)}
247 ${self.gravatar(email, size, tooltip=tooltip, tooltip_alt=contact, user=rc_user)}
248 <span class="${('user user-disabled' if show_disabled else 'user')}">
248 <span class="${('user user-disabled' if show_disabled else 'user')}">
249 ${h.link_to_user(rc_user or contact)}
249 ${h.link_to_user(rc_user or contact)}
250 </span>
250 </span>
251 </div>
251 </div>
252 </%def>
252 </%def>
253
253
254
254
255 <%def name="user_group_icon(user_group=None, size=16, tooltip=False)">
255 <%def name="user_group_icon(user_group=None, size=16, tooltip=False)">
256 <%
256 <%
257 if (size > 16):
257 if (size > 16):
258 gravatar_class = 'icon-user-group-alt'
258 gravatar_class = 'icon-user-group-alt'
259 else:
259 else:
260 gravatar_class = 'icon-user-group-alt'
260 gravatar_class = 'icon-user-group-alt'
261
261
262 if tooltip:
262 if tooltip:
263 gravatar_class += ' tooltip-hovercard'
263 gravatar_class += ' tooltip-hovercard'
264
264
265 data_hovercard_url = request.route_path('hovercard_user_group', user_group_id=user_group.users_group_id)
265 data_hovercard_url = request.route_path('hovercard_user_group', user_group_id=user_group.users_group_id)
266 %>
266 %>
267 <%doc>
267 <%doc>
268 TODO: johbo: For now we serve double size images to make it smooth
268 TODO: johbo: For now we serve double size images to make it smooth
269 for retina. This is how it worked until now. Should be replaced
269 for retina. This is how it worked until now. Should be replaced
270 with a better solution at some point.
270 with a better solution at some point.
271 </%doc>
271 </%doc>
272
272
273 <i style="font-size: ${size}px" class="${gravatar_class} x-icon-size-${size}" data-hovercard-url="${data_hovercard_url}"></i>
273 <i style="font-size: ${size}px" class="${gravatar_class} x-icon-size-${size}" data-hovercard-url="${data_hovercard_url}"></i>
274 </%def>
274 </%def>
275
275
276 <%def name="repo_page_title(repo_instance)">
276 <%def name="repo_page_title(repo_instance)">
277 <div class="title-content repo-title">
277 <div class="title-content repo-title">
278
278
279 <div class="title-main">
279 <div class="title-main">
280 ## SVN/HG/GIT icons
280 ## SVN/HG/GIT icons
281 %if h.is_hg(repo_instance):
281 %if h.is_hg(repo_instance):
282 <i class="icon-hg"></i>
282 <i class="icon-hg"></i>
283 %endif
283 %endif
284 %if h.is_git(repo_instance):
284 %if h.is_git(repo_instance):
285 <i class="icon-git"></i>
285 <i class="icon-git"></i>
286 %endif
286 %endif
287 %if h.is_svn(repo_instance):
287 %if h.is_svn(repo_instance):
288 <i class="icon-svn"></i>
288 <i class="icon-svn"></i>
289 %endif
289 %endif
290
290
291 ## public/private
291 ## public/private
292 %if repo_instance.private:
292 %if repo_instance.private:
293 <i class="icon-repo-private"></i>
293 <i class="icon-repo-private"></i>
294 %else:
294 %else:
295 <i class="icon-repo-public"></i>
295 <i class="icon-repo-public"></i>
296 %endif
296 %endif
297
297
298 ## repo name with group name
298 ## repo name with group name
299 ${h.breadcrumb_repo_link(repo_instance)}
299 ${h.breadcrumb_repo_link(repo_instance)}
300
300
301 ## Context Actions
301 ## Context Actions
302 <div class="pull-right">
302 <div class="pull-right">
303 %if c.rhodecode_user.username != h.DEFAULT_USER:
303 %if c.rhodecode_user.username != h.DEFAULT_USER:
304 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
304 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
305
305
306 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
306 <a href="#WatchRepo" onclick="toggleFollowingRepo(this, templateContext.repo_id); return false" title="${_('Watch this Repository and actions on it in your personalized journal')}" class="btn btn-sm ${('watching' if c.repository_is_user_following else '')}">
307 % if c.repository_is_user_following:
307 % if c.repository_is_user_following:
308 <i class="icon-eye-off"></i>${_('Unwatch')}
308 <i class="icon-eye-off"></i>${_('Unwatch')}
309 % else:
309 % else:
310 <i class="icon-eye"></i>${_('Watch')}
310 <i class="icon-eye"></i>${_('Watch')}
311 % endif
311 % endif
312
312
313 </a>
313 </a>
314 %else:
314 %else:
315 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
315 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_uid)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
316 %endif
316 %endif
317 </div>
317 </div>
318
318
319 </div>
319 </div>
320
320
321 ## FORKED
321 ## FORKED
322 %if repo_instance.fork:
322 %if repo_instance.fork:
323 <p class="discreet">
323 <p class="discreet">
324 <i class="icon-code-fork"></i> ${_('Fork of')}
324 <i class="icon-code-fork"></i> ${_('Fork of')}
325 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
325 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
326 </p>
326 </p>
327 %endif
327 %endif
328
328
329 ## IMPORTED FROM REMOTE
329 ## IMPORTED FROM REMOTE
330 %if repo_instance.clone_uri:
330 %if repo_instance.clone_uri:
331 <p class="discreet">
331 <p class="discreet">
332 <i class="icon-code-fork"></i> ${_('Clone from')}
332 <i class="icon-code-fork"></i> ${_('Clone from')}
333 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
333 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
334 </p>
334 </p>
335 %endif
335 %endif
336
336
337 ## LOCKING STATUS
337 ## LOCKING STATUS
338 %if repo_instance.locked[0]:
338 %if repo_instance.locked[0]:
339 <p class="locking_locked discreet">
339 <p class="locking_locked discreet">
340 <i class="icon-repo-lock"></i>
340 <i class="icon-repo-lock"></i>
341 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
341 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
342 </p>
342 </p>
343 %elif repo_instance.enable_locking:
343 %elif repo_instance.enable_locking:
344 <p class="locking_unlocked discreet">
344 <p class="locking_unlocked discreet">
345 <i class="icon-repo-unlock"></i>
345 <i class="icon-repo-unlock"></i>
346 ${_('Repository not locked. Pull repository to lock it.')}
346 ${_('Repository not locked. Pull repository to lock it.')}
347 </p>
347 </p>
348 %endif
348 %endif
349
349
350 </div>
350 </div>
351 </%def>
351 </%def>
352
352
353 <%def name="repo_menu(active=None)">
353 <%def name="repo_menu(active=None)">
354 <%
354 <%
355 ## determine if we have "any" option available
355 ## determine if we have "any" option available
356 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
356 can_lock = h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking
357 has_actions = can_lock
357 has_actions = can_lock
358
358
359 %>
359 %>
360 % if c.rhodecode_db_repo.archived:
360 % if c.rhodecode_db_repo.archived:
361 <div class="alert alert-warning text-center">
361 <div class="alert alert-warning text-center">
362 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
362 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
363 </div>
363 </div>
364 % endif
364 % endif
365
365
366 <!--- REPO CONTEXT BAR -->
366 <!--- REPO CONTEXT BAR -->
367 <div id="context-bar">
367 <div id="context-bar">
368 <div class="wrapper">
368 <div class="wrapper">
369
369
370 <div class="title">
370 <div class="title">
371 ${self.repo_page_title(c.rhodecode_db_repo)}
371 ${self.repo_page_title(c.rhodecode_db_repo)}
372 </div>
372 </div>
373
373
374 <ul id="context-pages" class="navigation horizontal-list">
374 <ul id="context-pages" class="navigation horizontal-list">
375 <li class="${h.is_active('summary', active)}"><a class="menulink" href="${h.route_path('repo_summary_explicit', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
375 <li class="${h.is_active('summary', active)}"><a class="menulink" href="${h.route_path('repo_summary_explicit', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
376 <li class="${h.is_active('commits', active)}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
376 <li class="${h.is_active('commits', active)}"><a class="menulink" href="${h.route_path('repo_commits', repo_name=c.repo_name)}"><div class="menulabel">${_('Commits')}</div></a></li>
377 <li class="${h.is_active('files', active)}"><a class="menulink" href="${h.repo_files_by_ref_url(c.repo_name, c.rhodecode_db_repo.repo_type, f_path='', ref_name=c.rhodecode_db_repo.landing_ref_name, commit_id='tip', query={'at':c.rhodecode_db_repo.landing_ref_name})}"><div class="menulabel">${_('Files')}</div></a></li>
377 <li class="${h.is_active('files', active)}"><a class="menulink" href="${h.repo_files_by_ref_url(c.repo_name, c.rhodecode_db_repo.repo_type, f_path='', ref_name=c.rhodecode_db_repo.landing_ref_name, commit_id='tip', query={'at':c.rhodecode_db_repo.landing_ref_name})}"><div class="menulabel">${_('Files')}</div></a></li>
378 <li class="${h.is_active('compare', active)}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
378 <li class="${h.is_active('compare', active)}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
379
379
380 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
380 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
381 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
381 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
382 <li class="${h.is_active('showpullrequest', active)}">
382 <li class="${h.is_active('showpullrequest', active)}">
383 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
383 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
384 <div class="menulabel">
384 <div class="menulabel">
385 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
385 ${_('Pull Requests')} <span class="menulink-counter">${c.repository_pull_requests}</span>
386 </div>
386 </div>
387 </a>
387 </a>
388 </li>
388 </li>
389 %endif
389 %endif
390
390
391 <li class="${h.is_active('artifacts', active)}">
391 <li class="${h.is_active('artifacts', active)}">
392 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
392 <a class="menulink" href="${h.route_path('repo_artifacts_list',repo_name=c.repo_name)}">
393 <div class="menulabel">
393 <div class="menulabel">
394 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
394 ${_('Artifacts')} <span class="menulink-counter">${c.repository_artifacts}</span>
395 </div>
395 </div>
396 </a>
396 </a>
397 </li>
397 </li>
398
398
399 %if not c.rhodecode_db_repo.archived and h.HasRepoPermissionAll('repository.admin')(c.repo_name):
399 %if not c.rhodecode_db_repo.archived and h.HasRepoPermissionAll('repository.admin')(c.repo_name):
400 <li class="${h.is_active('settings', active)}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
400 <li class="${h.is_active('settings', active)}"><a class="menulink" href="${h.route_path('edit_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Repository Settings')}</div></a></li>
401 %endif
401 %endif
402
402
403 <li class="${h.is_active('options', active)}">
403 <li class="${h.is_active('options', active)}">
404 % if has_actions:
404 % if has_actions:
405 <a class="menulink dropdown">
405 <a class="menulink dropdown">
406 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
406 <div class="menulabel">${_('Options')}<div class="show_more"></div></div>
407 </a>
407 </a>
408 <ul class="submenu">
408 <ul class="submenu">
409 %if can_lock:
409 %if can_lock:
410 %if c.rhodecode_db_repo.locked[0]:
410 %if c.rhodecode_db_repo.locked[0]:
411 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
411 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock Repository')}</a></li>
412 %else:
412 %else:
413 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
413 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock Repository')}</a></li>
414 %endif
414 %endif
415 %endif
415 %endif
416 </ul>
416 </ul>
417 % endif
417 % endif
418 </li>
418 </li>
419
419
420 </ul>
420 </ul>
421 </div>
421 </div>
422 <div class="clear"></div>
422 <div class="clear"></div>
423 </div>
423 </div>
424
424
425 <!--- REPO END CONTEXT BAR -->
425 <!--- REPO END CONTEXT BAR -->
426
426
427 </%def>
427 </%def>
428
428
429 <%def name="repo_group_page_title(repo_group_instance)">
429 <%def name="repo_group_page_title(repo_group_instance)">
430 <div class="title-content">
430 <div class="title-content">
431 <div class="title-main">
431 <div class="title-main">
432 ## Repository Group icon
432 ## Repository Group icon
433 <i class="icon-repo-group"></i>
433 <i class="icon-repo-group"></i>
434
434
435 ## repo name with group name
435 ## repo name with group name
436 ${h.breadcrumb_repo_group_link(repo_group_instance)}
436 ${h.breadcrumb_repo_group_link(repo_group_instance)}
437 </div>
437 </div>
438
438
439 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
439 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
440 <div class="repo-group-desc discreet">
440 <div class="repo-group-desc discreet">
441 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
441 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
442 </div>
442 </div>
443
443
444 </div>
444 </div>
445 </%def>
445 </%def>
446
446
447
447
448 <%def name="repo_group_menu(active=None)">
448 <%def name="repo_group_menu(active=None)">
449 <%
449 <%
450 gr_name = c.repo_group.group_name if c.repo_group else None
450 gr_name = c.repo_group.group_name if c.repo_group else None
451 # create repositories with write permission on group is set to true
451 # create repositories with write permission on group is set to true
452 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
452 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
453
453
454 %>
454 %>
455
455
456
456
457 <!--- REPO GROUP CONTEXT BAR -->
457 <!--- REPO GROUP CONTEXT BAR -->
458 <div id="context-bar">
458 <div id="context-bar">
459 <div class="wrapper">
459 <div class="wrapper">
460 <div class="title">
460 <div class="title">
461 ${self.repo_group_page_title(c.repo_group)}
461 ${self.repo_group_page_title(c.repo_group)}
462 </div>
462 </div>
463
463
464 <ul id="context-pages" class="navigation horizontal-list">
464 <ul id="context-pages" class="navigation horizontal-list">
465 <li class="${h.is_active('home', active)}">
465 <li class="${h.is_active('home', active)}">
466 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
466 <a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a>
467 </li>
467 </li>
468 % if c.is_super_admin or group_admin:
468 % if c.is_super_admin or group_admin:
469 <li class="${h.is_active('settings', active)}">
469 <li class="${h.is_active('settings', active)}">
470 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
470 <a class="menulink" href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}"><div class="menulabel">${_('Group Settings')}</div></a>
471 </li>
471 </li>
472 % endif
472 % endif
473
473
474 </ul>
474 </ul>
475 </div>
475 </div>
476 <div class="clear"></div>
476 <div class="clear"></div>
477 </div>
477 </div>
478
478
479 <!--- REPO GROUP CONTEXT BAR -->
479 <!--- REPO GROUP CONTEXT BAR -->
480
480
481 </%def>
481 </%def>
482
482
483
483
484 <%def name="usermenu(active=False)">
484 <%def name="usermenu(active=False)">
485 <%
485 <%
486 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
486 not_anonymous = c.rhodecode_user.username != h.DEFAULT_USER
487
487
488 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
488 gr_name = c.repo_group.group_name if (hasattr(c, 'repo_group') and c.repo_group) else None
489 # create repositories with write permission on group is set to true
489 # create repositories with write permission on group is set to true
490
490
491 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
491 can_fork = c.is_super_admin or h.HasPermissionAny('hg.fork.repository')()
492 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
492 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
493 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
493 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
494 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
494 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
495
495
496 can_create_repos = c.is_super_admin or c.can_create_repo
496 can_create_repos = c.is_super_admin or c.can_create_repo
497 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
497 can_create_repo_groups = c.is_super_admin or c.can_create_repo_group
498
498
499 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
499 can_create_repos_in_group = c.is_super_admin or group_admin or (group_write and create_on_write)
500 can_create_repo_groups_in_group = c.is_super_admin or group_admin
500 can_create_repo_groups_in_group = c.is_super_admin or group_admin
501 %>
501 %>
502
502
503 % if not_anonymous:
503 % if not_anonymous:
504 <%
504 <%
505 default_target_group = dict()
505 default_target_group = dict()
506 if c.rhodecode_user.personal_repo_group:
506 if c.rhodecode_user.personal_repo_group:
507 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
507 default_target_group = dict(parent_group=c.rhodecode_user.personal_repo_group.group_id)
508 %>
508 %>
509
509
510 ## create action
510 ## create action
511 <li>
511 <li>
512 <a href="#create-actions" onclick="return false;" class="menulink childs">
512 <a href="#create-actions" onclick="return false;" class="menulink childs">
513 <i class="icon-plus-circled"></i>
513 <i class="icon-plus-circled"></i>
514 </a>
514 </a>
515
515
516 <div class="action-menu submenu">
516 <div class="action-menu submenu">
517
517
518 <ol>
518 <ol>
519 ## scope of within a repository
519 ## scope of within a repository
520 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
520 % if hasattr(c, 'rhodecode_db_repo') and c.rhodecode_db_repo:
521 <li class="submenu-title">${_('This Repository')}</li>
521 <li class="submenu-title">${_('This Repository')}</li>
522 <li>
522 <li>
523 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
523 <a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a>
524 </li>
524 </li>
525 % if can_fork:
525 % if can_fork:
526 <li>
526 <li>
527 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
527 <a href="${h.route_path('repo_fork_new',repo_name=c.repo_name,_query=default_target_group)}">${_('Fork this repository')}</a>
528 </li>
528 </li>
529 % endif
529 % endif
530 % endif
530 % endif
531
531
532 ## scope of within repository groups
532 ## scope of within repository groups
533 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
533 % if hasattr(c, 'repo_group') and c.repo_group and (can_create_repos_in_group or can_create_repo_groups_in_group):
534 <li class="submenu-title">${_('This Repository Group')}</li>
534 <li class="submenu-title">${_('This Repository Group')}</li>
535
535
536 % if can_create_repos_in_group:
536 % if can_create_repos_in_group:
537 <li>
537 <li>
538 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('New Repository')}</a>
538 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('New Repository')}</a>
539 </li>
539 </li>
540 % endif
540 % endif
541
541
542 % if can_create_repo_groups_in_group:
542 % if can_create_repo_groups_in_group:
543 <li>
543 <li>
544 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'New Repository Group')}</a>
544 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'New Repository Group')}</a>
545 </li>
545 </li>
546 % endif
546 % endif
547 % endif
547 % endif
548
548
549 ## personal group
549 ## personal group
550 % if c.rhodecode_user.personal_repo_group:
550 % if c.rhodecode_user.personal_repo_group:
551 <li class="submenu-title">Personal Group</li>
551 <li class="submenu-title">Personal Group</li>
552
552
553 <li>
553 <li>
554 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
554 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}" >${_('New Repository')} </a>
555 </li>
555 </li>
556
556
557 <li>
557 <li>
558 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
558 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.rhodecode_user.personal_repo_group.group_id))}">${_('New Repository Group')} </a>
559 </li>
559 </li>
560 % endif
560 % endif
561
561
562 ## Global actions
562 ## Global actions
563 <li class="submenu-title">RhodeCode</li>
563 <li class="submenu-title">RhodeCode</li>
564 % if can_create_repos:
564 % if can_create_repos:
565 <li>
565 <li>
566 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
566 <a href="${h.route_path('repo_new')}" >${_('New Repository')}</a>
567 </li>
567 </li>
568 % endif
568 % endif
569
569
570 % if can_create_repo_groups:
570 % if can_create_repo_groups:
571 <li>
571 <li>
572 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
572 <a href="${h.route_path('repo_group_new')}" >${_(u'New Repository Group')}</a>
573 </li>
573 </li>
574 % endif
574 % endif
575
575
576 <li>
576 <li>
577 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
577 <a href="${h.route_path('gists_new')}">${_(u'New Gist')}</a>
578 </li>
578 </li>
579
579
580 </ol>
580 </ol>
581
581
582 </div>
582 </div>
583 </li>
583 </li>
584
584
585 ## notifications
585 ## notifications
586 <li>
586 <li>
587 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
587 <a class="${('empty' if c.unread_notifications == 0 else '')}" href="${h.route_path('notifications_show_all')}">
588 ${c.unread_notifications}
588 ${c.unread_notifications}
589 </a>
589 </a>
590 </li>
590 </li>
591 % endif
591 % endif
592
592
593 ## USER MENU
593 ## USER MENU
594 <li id="quick_login_li" class="${'active' if active else ''}">
594 <li id="quick_login_li" class="${'active' if active else ''}">
595 % if c.rhodecode_user.username == h.DEFAULT_USER:
595 % if c.rhodecode_user.username == h.DEFAULT_USER:
596 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
596 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
597 ${gravatar(c.rhodecode_user.email, 20)}
597 ${gravatar(c.rhodecode_user.email, 20)}
598 <span class="user">
598 <span class="user">
599 <span>${_('Sign in')}</span>
599 <span>${_('Sign in')}</span>
600 </span>
600 </span>
601 </a>
601 </a>
602 % else:
602 % else:
603 ## logged in user
603 ## logged in user
604 <a id="quick_login_link" class="menulink childs">
604 <a id="quick_login_link" class="menulink childs">
605 ${gravatar(c.rhodecode_user.email, 20)}
605 ${gravatar(c.rhodecode_user.email, 20)}
606 <span class="user">
606 <span class="user">
607 <span class="menu_link_user">${c.rhodecode_user.username}</span>
607 <span class="menu_link_user">${c.rhodecode_user.username}</span>
608 <div class="show_more"></div>
608 <div class="show_more"></div>
609 </span>
609 </span>
610 </a>
610 </a>
611 ## subnav with menu for logged in user
611 ## subnav with menu for logged in user
612 <div class="user-menu submenu">
612 <div class="user-menu submenu">
613 <div id="quick_login">
613 <div id="quick_login">
614 %if c.rhodecode_user.username != h.DEFAULT_USER:
614 %if c.rhodecode_user.username != h.DEFAULT_USER:
615 <div class="">
615 <div class="">
616 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
616 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
617 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
617 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
618 <div class="email">${c.rhodecode_user.email}</div>
618 <div class="email">${c.rhodecode_user.email}</div>
619 </div>
619 </div>
620 <div class="">
620 <div class="">
621 <ol class="links">
621 <ol class="links">
622 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
622 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
623 % if c.rhodecode_user.personal_repo_group:
623 % if c.rhodecode_user.personal_repo_group:
624 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
624 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
625 % endif
625 % endif
626 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
626 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
627
627
628 % if c.debug_style:
628 % if c.debug_style:
629 <li>
629 <li>
630 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
630 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
631 <div class="menulabel">${_('[Style]')}</div>
631 <div class="menulabel">${_('[Style]')}</div>
632 </a>
632 </a>
633 </li>
633 </li>
634 % endif
634 % endif
635
635
636 ## bookmark-items
636 ## bookmark-items
637 <li class="bookmark-items">
637 <li class="bookmark-items">
638 ${_('Bookmarks')}
638 ${_('Bookmarks')}
639 <div class="pull-right">
639 <div class="pull-right">
640 <a href="${h.route_path('my_account_bookmarks')}">
640 <a href="${h.route_path('my_account_bookmarks')}">
641
641
642 <i class="icon-cog"></i>
642 <i class="icon-cog"></i>
643 </a>
643 </a>
644 </div>
644 </div>
645 </li>
645 </li>
646 % if not c.bookmark_items:
646 % if not c.bookmark_items:
647 <li>
647 <li>
648 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
648 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
649 </li>
649 </li>
650 % endif
650 % endif
651 % for item in c.bookmark_items:
651 % for item in c.bookmark_items:
652 <li>
652 <li>
653 % if item.repository:
653 % if item.repository:
654 <div>
654 <div>
655 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
655 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
656 <code>${item.position}</code>
656 <code>${item.position}</code>
657 % if item.repository.repo_type == 'hg':
657 % if item.repository.repo_type == 'hg':
658 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
658 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
659 % elif item.repository.repo_type == 'git':
659 % elif item.repository.repo_type == 'git':
660 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
660 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
661 % elif item.repository.repo_type == 'svn':
661 % elif item.repository.repo_type == 'svn':
662 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
662 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
663 % endif
663 % endif
664 ${(item.title or h.shorter(item.repository.repo_name, 30))}
664 ${(item.title or h.shorter(item.repository.repo_name, 30))}
665 </a>
665 </a>
666 </div>
666 </div>
667 % elif item.repository_group:
667 % elif item.repository_group:
668 <div>
668 <div>
669 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
669 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
670 <code>${item.position}</code>
670 <code>${item.position}</code>
671 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
671 <i class="icon-repo-group" title="${_('Repository group')}" style="font-size: 14px"></i>
672 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
672 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
673 </a>
673 </a>
674 </div>
674 </div>
675 % else:
675 % else:
676 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
676 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
677 <code>${item.position}</code>
677 <code>${item.position}</code>
678 ${item.title}
678 ${item.title}
679 </a>
679 </a>
680 % endif
680 % endif
681 </li>
681 </li>
682 % endfor
682 % endfor
683
683
684 <li class="logout">
684 <li class="logout">
685 ${h.secure_form(h.route_path('logout'), request=request)}
685 ${h.secure_form(h.route_path('logout'), request=request)}
686 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
686 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
687 ${h.end_form()}
687 ${h.end_form()}
688 </li>
688 </li>
689 </ol>
689 </ol>
690 </div>
690 </div>
691 %endif
691 %endif
692 </div>
692 </div>
693 </div>
693 </div>
694
694
695 % endif
695 % endif
696 </li>
696 </li>
697 </%def>
697 </%def>
698
698
699 <%def name="menu_items(active=None)">
699 <%def name="menu_items(active=None)">
700 <%
700 <%
701 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
701 notice_messages, notice_level = c.rhodecode_user.get_notice_messages()
702 notice_display = 'none' if len(notice_messages) == 0 else ''
702 notice_display = 'none' if len(notice_messages) == 0 else ''
703 %>
703 %>
704 <style>
705
706 </style>
707
704
708 <ul id="quick" class="main_nav navigation horizontal-list">
705 <ul id="quick" class="main_nav navigation horizontal-list">
709 ## notice box for important system messages
706 ## notice box for important system messages
710 <li style="display: ${notice_display}">
707 <li style="display: ${notice_display}">
711 <a class="notice-box" href="#openNotice" onclick="$('.notice-messages-container').toggle(); return false">
708 <a class="notice-box" href="#openNotice" onclick="$('.notice-messages-container').toggle(); return false">
712 <div class="menulabel-notice ${notice_level}" >
709 <div class="menulabel-notice ${notice_level}" >
713 ${len(notice_messages)}
710 ${len(notice_messages)}
714 </div>
711 </div>
715 </a>
712 </a>
716 </li>
713 </li>
717 <div class="notice-messages-container" style="display: none">
714 <div class="notice-messages-container" style="display: none">
718 <div class="notice-messages">
715 <div class="notice-messages">
719 <table class="rctable">
716 <table class="rctable">
720 % for notice in notice_messages:
717 % for notice in notice_messages:
721 <tr id="notice-message-${notice['msg_id']}" class="notice-message-${notice['level']}">
718 <tr id="notice-message-${notice['msg_id']}" class="notice-message-${notice['level']}">
722 <td style="vertical-align: text-top; width: 20px">
719 <td style="vertical-align: text-top; width: 20px">
723 <i class="tooltip icon-info notice-color-${notice['level']}" title="${notice['level']}"></i>
720 <i class="tooltip icon-info notice-color-${notice['level']}" title="${notice['level']}"></i>
724 </td>
721 </td>
725 <td>
722 <td>
726 <span><i class="icon-plus-squared cursor-pointer" onclick="$('#notice-${notice['msg_id']}').toggle()"></i> </span>
723 <span><i class="icon-plus-squared cursor-pointer" onclick="$('#notice-${notice['msg_id']}').toggle()"></i> </span>
727 ${notice['subject']}
724 ${notice['subject']}
728
725
729 <div id="notice-${notice['msg_id']}" style="display: none">
726 <div id="notice-${notice['msg_id']}" style="display: none">
730 ${h.render(notice['body'], renderer='markdown')}
727 ${h.render(notice['body'], renderer='markdown')}
731 </div>
728 </div>
732 </td>
729 </td>
733 <td style="vertical-align: text-top; width: 35px;">
730 <td style="vertical-align: text-top; width: 35px;">
734 <a class="tooltip" title="${_('dismiss')}" href="#dismiss" onclick="dismissNotice(${notice['msg_id']});return false">
731 <a class="tooltip" title="${_('dismiss')}" href="#dismiss" onclick="dismissNotice(${notice['msg_id']});return false">
735 <i class="icon-remove icon-filled-red"></i>
732 <i class="icon-remove icon-filled-red"></i>
736 </a>
733 </a>
737 </td>
734 </td>
738 </tr>
735 </tr>
739
736
740 % endfor
737 % endfor
741 </table>
738 </table>
742 </div>
739 </div>
743 </div>
740 </div>
744 ## Main filter
741 ## Main filter
745 <li>
742 <li>
746 <div class="menulabel main_filter_box">
743 <div class="menulabel main_filter_box">
747 <div class="main_filter_input_box">
744 <div class="main_filter_input_box">
748 <ul class="searchItems">
745 <ul class="searchItems">
749
746
750 <li class="searchTag searchTagIcon">
747 <li class="searchTag searchTagIcon">
751 <i class="icon-search"></i>
748 <i class="icon-search"></i>
752 </li>
749 </li>
753
750
754 % if c.template_context['search_context']['repo_id']:
751 % if c.template_context['search_context']['repo_id']:
755 <li class="searchTag searchTagFilter searchTagHidable" >
752 <li class="searchTag searchTagFilter searchTagHidable" >
756 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
753 ##<a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">
757 <span class="tag">
754 <span class="tag">
758 This repo
755 This repo
759 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
756 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
760 </span>
757 </span>
761 ##</a>
758 ##</a>
762 </li>
759 </li>
763 % elif c.template_context['search_context']['repo_group_id']:
760 % elif c.template_context['search_context']['repo_group_id']:
764 <li class="searchTag searchTagFilter searchTagHidable">
761 <li class="searchTag searchTagFilter searchTagHidable">
765 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
762 ##<a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">
766 <span class="tag">
763 <span class="tag">
767 This group
764 This group
768 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
765 <a href="#removeGoToFilter" onclick="removeGoToFilter(); return false"><i class="icon-cancel-circled"></i></a>
769 </span>
766 </span>
770 ##</a>
767 ##</a>
771 </li>
768 </li>
772 % endif
769 % endif
773
770
774 <li class="searchTagInput">
771 <li class="searchTagInput">
775 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
772 <input class="main_filter_input" id="main_filter" size="25" type="text" name="main_filter" placeholder="${_('search / go to...')}" value="" />
776 </li>
773 </li>
777 <li class="searchTag searchTagHelp">
774 <li class="searchTag searchTagHelp">
778 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
775 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
779 </li>
776 </li>
780 </ul>
777 </ul>
781 </div>
778 </div>
782 </div>
779 </div>
783
780
784 <div id="main_filter_help" style="display: none">
781 <div id="main_filter_help" style="display: none">
785 - Use '/' key to quickly access this field.
782 - Use '/' key to quickly access this field.
786
783
787 - Enter a name of repository, or repository group for quick search.
784 - Enter a name of repository, or repository group for quick search.
788
785
789 - Prefix query to allow special search:
786 - Prefix query to allow special search:
790
787
791 user:admin, to search for usernames, always global
788 user:admin, to search for usernames, always global
792
789
793 user_group:devops, to search for user groups, always global
790 user_group:devops, to search for user groups, always global
794
791
795 pr:303, to search for pull request number, title, or description, always global
792 pr:303, to search for pull request number, title, or description, always global
796
793
797 commit:efced4, to search for commits, scoped to repositories or groups
794 commit:efced4, to search for commits, scoped to repositories or groups
798
795
799 file:models.py, to search for file paths, scoped to repositories or groups
796 file:models.py, to search for file paths, scoped to repositories or groups
800
797
801 % if c.template_context['search_context']['repo_id']:
798 % if c.template_context['search_context']['repo_id']:
802 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
799 For advanced full text search visit: <a href="${h.route_path('search_repo',repo_name=c.template_context['search_context']['repo_name'])}">repository search</a>
803 % elif c.template_context['search_context']['repo_group_id']:
800 % elif c.template_context['search_context']['repo_group_id']:
804 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
801 For advanced full text search visit: <a href="${h.route_path('search_repo_group',repo_group_name=c.template_context['search_context']['repo_group_name'])}">repository group search</a>
805 % else:
802 % else:
806 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
803 For advanced full text search visit: <a href="${h.route_path('search')}">global search</a>
807 % endif
804 % endif
808 </div>
805 </div>
809 </li>
806 </li>
810
807
811 ## ROOT MENU
808 ## ROOT MENU
812 <li class="${h.is_active('home', active)}">
809 <li class="${h.is_active('home', active)}">
813 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
810 <a class="menulink" title="${_('Home')}" href="${h.route_path('home')}">
814 <div class="menulabel">${_('Home')}</div>
811 <div class="menulabel">${_('Home')}</div>
815 </a>
812 </a>
816 </li>
813 </li>
817
814
818 %if c.rhodecode_user.username != h.DEFAULT_USER:
815 %if c.rhodecode_user.username != h.DEFAULT_USER:
819 <li class="${h.is_active('journal', active)}">
816 <li class="${h.is_active('journal', active)}">
820 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
817 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
821 <div class="menulabel">${_('Journal')}</div>
818 <div class="menulabel">${_('Journal')}</div>
822 </a>
819 </a>
823 </li>
820 </li>
824 %else:
821 %else:
825 <li class="${h.is_active('journal', active)}">
822 <li class="${h.is_active('journal', active)}">
826 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
823 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
827 <div class="menulabel">${_('Public journal')}</div>
824 <div class="menulabel">${_('Public journal')}</div>
828 </a>
825 </a>
829 </li>
826 </li>
830 %endif
827 %endif
831
828
832 <li class="${h.is_active('gists', active)}">
829 <li class="${h.is_active('gists', active)}">
833 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
830 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
834 <div class="menulabel">${_('Gists')}</div>
831 <div class="menulabel">${_('Gists')}</div>
835 </a>
832 </a>
836 </li>
833 </li>
837
834
838 % if c.is_super_admin or c.is_delegated_admin:
835 % if c.is_super_admin or c.is_delegated_admin:
839 <li class="${h.is_active('admin', active)}">
836 <li class="${h.is_active('admin', active)}">
840 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
837 <a class="menulink childs" title="${_('Admin settings')}" href="${h.route_path('admin_home')}">
841 <div class="menulabel">${_('Admin')} </div>
838 <div class="menulabel">${_('Admin')} </div>
842 </a>
839 </a>
843 </li>
840 </li>
844 % endif
841 % endif
845
842
846 ## render extra user menu
843 ## render extra user menu
847 ${usermenu(active=(active=='my_account'))}
844 ${usermenu(active=(active=='my_account'))}
848
845
849 </ul>
846 </ul>
850
847
851 <script type="text/javascript">
848 <script type="text/javascript">
852 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
849 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
853
850
854 var formatRepoResult = function(result, container, query, escapeMarkup) {
851 var formatRepoResult = function(result, container, query, escapeMarkup) {
855 return function(data, escapeMarkup) {
852 return function(data, escapeMarkup) {
856 if (!data.repo_id){
853 if (!data.repo_id){
857 return data.text; // optgroup text Repositories
854 return data.text; // optgroup text Repositories
858 }
855 }
859
856
860 var tmpl = '';
857 var tmpl = '';
861 var repoType = data['repo_type'];
858 var repoType = data['repo_type'];
862 var repoName = data['text'];
859 var repoName = data['text'];
863
860
864 if(data && data.type == 'repo'){
861 if(data && data.type == 'repo'){
865 if(repoType === 'hg'){
862 if(repoType === 'hg'){
866 tmpl += '<i class="icon-hg"></i> ';
863 tmpl += '<i class="icon-hg"></i> ';
867 }
864 }
868 else if(repoType === 'git'){
865 else if(repoType === 'git'){
869 tmpl += '<i class="icon-git"></i> ';
866 tmpl += '<i class="icon-git"></i> ';
870 }
867 }
871 else if(repoType === 'svn'){
868 else if(repoType === 'svn'){
872 tmpl += '<i class="icon-svn"></i> ';
869 tmpl += '<i class="icon-svn"></i> ';
873 }
870 }
874 if(data['private']){
871 if(data['private']){
875 tmpl += '<i class="icon-lock" ></i> ';
872 tmpl += '<i class="icon-lock" ></i> ';
876 }
873 }
877 else if(visualShowPublicIcon){
874 else if(visualShowPublicIcon){
878 tmpl += '<i class="icon-unlock-alt"></i> ';
875 tmpl += '<i class="icon-unlock-alt"></i> ';
879 }
876 }
880 }
877 }
881 tmpl += escapeMarkup(repoName);
878 tmpl += escapeMarkup(repoName);
882 return tmpl;
879 return tmpl;
883
880
884 }(result, escapeMarkup);
881 }(result, escapeMarkup);
885 };
882 };
886
883
887 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
884 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
888 return function(data, escapeMarkup) {
885 return function(data, escapeMarkup) {
889 if (!data.repo_group_id){
886 if (!data.repo_group_id){
890 return data.text; // optgroup text Repositories
887 return data.text; // optgroup text Repositories
891 }
888 }
892
889
893 var tmpl = '';
890 var tmpl = '';
894 var repoGroupName = data['text'];
891 var repoGroupName = data['text'];
895
892
896 if(data){
893 if(data){
897
894
898 tmpl += '<i class="icon-repo-group"></i> ';
895 tmpl += '<i class="icon-repo-group"></i> ';
899
896
900 }
897 }
901 tmpl += escapeMarkup(repoGroupName);
898 tmpl += escapeMarkup(repoGroupName);
902 return tmpl;
899 return tmpl;
903
900
904 }(result, escapeMarkup);
901 }(result, escapeMarkup);
905 };
902 };
906
903
907 var escapeRegExChars = function (value) {
904 var escapeRegExChars = function (value) {
908 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
905 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
909 };
906 };
910
907
911 var getRepoIcon = function(repo_type) {
908 var getRepoIcon = function(repo_type) {
912 if (repo_type === 'hg') {
909 if (repo_type === 'hg') {
913 return '<i class="icon-hg"></i> ';
910 return '<i class="icon-hg"></i> ';
914 }
911 }
915 else if (repo_type === 'git') {
912 else if (repo_type === 'git') {
916 return '<i class="icon-git"></i> ';
913 return '<i class="icon-git"></i> ';
917 }
914 }
918 else if (repo_type === 'svn') {
915 else if (repo_type === 'svn') {
919 return '<i class="icon-svn"></i> ';
916 return '<i class="icon-svn"></i> ';
920 }
917 }
921 return ''
918 return ''
922 };
919 };
923
920
924 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
921 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
925
922
926 if (value.split(':').length === 2) {
923 if (value.split(':').length === 2) {
927 value = value.split(':')[1]
924 value = value.split(':')[1]
928 }
925 }
929
926
930 var searchType = data['type'];
927 var searchType = data['type'];
931 var searchSubType = data['subtype'];
928 var searchSubType = data['subtype'];
932 var valueDisplay = data['value_display'];
929 var valueDisplay = data['value_display'];
933 var valueIcon = data['value_icon'];
930 var valueIcon = data['value_icon'];
934
931
935 var pattern = '(' + escapeRegExChars(value) + ')';
932 var pattern = '(' + escapeRegExChars(value) + ')';
936
933
937 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
934 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
938
935
939 // highlight match
936 // highlight match
940 if (searchType != 'text') {
937 if (searchType != 'text') {
941 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
938 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
942 }
939 }
943
940
944 var icon = '';
941 var icon = '';
945
942
946 if (searchType === 'hint') {
943 if (searchType === 'hint') {
947 icon += '<i class="icon-repo-group"></i> ';
944 icon += '<i class="icon-repo-group"></i> ';
948 }
945 }
949 // full text search/hints
946 // full text search/hints
950 else if (searchType === 'search') {
947 else if (searchType === 'search') {
951 if (valueIcon === undefined) {
948 if (valueIcon === undefined) {
952 icon += '<i class="icon-more"></i> ';
949 icon += '<i class="icon-more"></i> ';
953 } else {
950 } else {
954 icon += valueIcon + ' ';
951 icon += valueIcon + ' ';
955 }
952 }
956
953
957 if (searchSubType !== undefined && searchSubType == 'repo') {
954 if (searchSubType !== undefined && searchSubType == 'repo') {
958 valueDisplay += '<div class="pull-right tag">repository</div>';
955 valueDisplay += '<div class="pull-right tag">repository</div>';
959 }
956 }
960 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
957 else if (searchSubType !== undefined && searchSubType == 'repo_group') {
961 valueDisplay += '<div class="pull-right tag">repo group</div>';
958 valueDisplay += '<div class="pull-right tag">repo group</div>';
962 }
959 }
963 }
960 }
964 // repository
961 // repository
965 else if (searchType === 'repo') {
962 else if (searchType === 'repo') {
966
963
967 var repoIcon = getRepoIcon(data['repo_type']);
964 var repoIcon = getRepoIcon(data['repo_type']);
968 icon += repoIcon;
965 icon += repoIcon;
969
966
970 if (data['private']) {
967 if (data['private']) {
971 icon += '<i class="icon-lock" ></i> ';
968 icon += '<i class="icon-lock" ></i> ';
972 }
969 }
973 else if (visualShowPublicIcon) {
970 else if (visualShowPublicIcon) {
974 icon += '<i class="icon-unlock-alt"></i> ';
971 icon += '<i class="icon-unlock-alt"></i> ';
975 }
972 }
976 }
973 }
977 // repository groups
974 // repository groups
978 else if (searchType === 'repo_group') {
975 else if (searchType === 'repo_group') {
979 icon += '<i class="icon-repo-group"></i> ';
976 icon += '<i class="icon-repo-group"></i> ';
980 }
977 }
981 // user group
978 // user group
982 else if (searchType === 'user_group') {
979 else if (searchType === 'user_group') {
983 icon += '<i class="icon-group"></i> ';
980 icon += '<i class="icon-group"></i> ';
984 }
981 }
985 // user
982 // user
986 else if (searchType === 'user') {
983 else if (searchType === 'user') {
987 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
984 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
988 }
985 }
989 // pull request
986 // pull request
990 else if (searchType === 'pull_request') {
987 else if (searchType === 'pull_request') {
991 icon += '<i class="icon-merge"></i> ';
988 icon += '<i class="icon-merge"></i> ';
992 }
989 }
993 // commit
990 // commit
994 else if (searchType === 'commit') {
991 else if (searchType === 'commit') {
995 var repo_data = data['repo_data'];
992 var repo_data = data['repo_data'];
996 var repoIcon = getRepoIcon(repo_data['repository_type']);
993 var repoIcon = getRepoIcon(repo_data['repository_type']);
997 if (repoIcon) {
994 if (repoIcon) {
998 icon += repoIcon;
995 icon += repoIcon;
999 } else {
996 } else {
1000 icon += '<i class="icon-tag"></i>';
997 icon += '<i class="icon-tag"></i>';
1001 }
998 }
1002 }
999 }
1003 // file
1000 // file
1004 else if (searchType === 'file') {
1001 else if (searchType === 'file') {
1005 var repo_data = data['repo_data'];
1002 var repo_data = data['repo_data'];
1006 var repoIcon = getRepoIcon(repo_data['repository_type']);
1003 var repoIcon = getRepoIcon(repo_data['repository_type']);
1007 if (repoIcon) {
1004 if (repoIcon) {
1008 icon += repoIcon;
1005 icon += repoIcon;
1009 } else {
1006 } else {
1010 icon += '<i class="icon-tag"></i>';
1007 icon += '<i class="icon-tag"></i>';
1011 }
1008 }
1012 }
1009 }
1013 // generic text
1010 // generic text
1014 else if (searchType === 'text') {
1011 else if (searchType === 'text') {
1015 icon = '';
1012 icon = '';
1016 }
1013 }
1017
1014
1018 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
1015 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
1019 return tmpl.format(icon, valueDisplay);
1016 return tmpl.format(icon, valueDisplay);
1020 };
1017 };
1021
1018
1022 var handleSelect = function(element, suggestion) {
1019 var handleSelect = function(element, suggestion) {
1023 if (suggestion.type === "hint") {
1020 if (suggestion.type === "hint") {
1024 // we skip action
1021 // we skip action
1025 $('#main_filter').focus();
1022 $('#main_filter').focus();
1026 }
1023 }
1027 else if (suggestion.type === "text") {
1024 else if (suggestion.type === "text") {
1028 // we skip action
1025 // we skip action
1029 $('#main_filter').focus();
1026 $('#main_filter').focus();
1030
1027
1031 } else {
1028 } else {
1032 window.location = suggestion['url'];
1029 window.location = suggestion['url'];
1033 }
1030 }
1034 };
1031 };
1035
1032
1036 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
1033 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
1037 if (queryLowerCase.split(':').length === 2) {
1034 if (queryLowerCase.split(':').length === 2) {
1038 queryLowerCase = queryLowerCase.split(':')[1]
1035 queryLowerCase = queryLowerCase.split(':')[1]
1039 }
1036 }
1040 if (suggestion.type === "text") {
1037 if (suggestion.type === "text") {
1041 // special case we don't want to "skip" display for
1038 // special case we don't want to "skip" display for
1042 return true
1039 return true
1043 }
1040 }
1044 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
1041 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
1045 };
1042 };
1046
1043
1047 var cleanContext = {
1044 var cleanContext = {
1048 repo_view_type: null,
1045 repo_view_type: null,
1049
1046
1050 repo_id: null,
1047 repo_id: null,
1051 repo_name: "",
1048 repo_name: "",
1052
1049
1053 repo_group_id: null,
1050 repo_group_id: null,
1054 repo_group_name: null
1051 repo_group_name: null
1055 };
1052 };
1056 var removeGoToFilter = function () {
1053 var removeGoToFilter = function () {
1057 $('.searchTagHidable').hide();
1054 $('.searchTagHidable').hide();
1058 $('#main_filter').autocomplete(
1055 $('#main_filter').autocomplete(
1059 'setOptions', {params:{search_context: cleanContext}});
1056 'setOptions', {params:{search_context: cleanContext}});
1060 };
1057 };
1061
1058
1062 $('#main_filter').autocomplete({
1059 $('#main_filter').autocomplete({
1063 serviceUrl: pyroutes.url('goto_switcher_data'),
1060 serviceUrl: pyroutes.url('goto_switcher_data'),
1064 params: {
1061 params: {
1065 "search_context": templateContext.search_context
1062 "search_context": templateContext.search_context
1066 },
1063 },
1067 minChars:2,
1064 minChars:2,
1068 maxHeight:400,
1065 maxHeight:400,
1069 deferRequestBy: 300, //miliseconds
1066 deferRequestBy: 300, //miliseconds
1070 tabDisabled: true,
1067 tabDisabled: true,
1071 autoSelectFirst: false,
1068 autoSelectFirst: false,
1072 containerClass: 'autocomplete-qfilter-suggestions',
1069 containerClass: 'autocomplete-qfilter-suggestions',
1073 formatResult: autocompleteMainFilterFormatResult,
1070 formatResult: autocompleteMainFilterFormatResult,
1074 lookupFilter: autocompleteMainFilterResult,
1071 lookupFilter: autocompleteMainFilterResult,
1075 onSelect: function (element, suggestion) {
1072 onSelect: function (element, suggestion) {
1076 handleSelect(element, suggestion);
1073 handleSelect(element, suggestion);
1077 return false;
1074 return false;
1078 },
1075 },
1079 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
1076 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
1080 if (jqXHR !== 'abort') {
1077 if (jqXHR !== 'abort') {
1081 var message = formatErrorMessage(jqXHR, textStatus, errorThrown);
1078 var message = formatErrorMessage(jqXHR, textStatus, errorThrown);
1082 SwalNoAnimation.fire({
1079 SwalNoAnimation.fire({
1083 icon: 'error',
1080 icon: 'error',
1084 title: _gettext('Error during search operation'),
1081 title: _gettext('Error during search operation'),
1085 html: '<span style="white-space: pre-line">{0}</span>'.format(message),
1082 html: '<span style="white-space: pre-line">{0}</span>'.format(message),
1086 }).then(function(result) {
1083 }).then(function(result) {
1087 window.location.reload();
1084 window.location.reload();
1088 })
1085 })
1089 }
1086 }
1090 },
1087 },
1091 onSearchStart: function (params) {
1088 onSearchStart: function (params) {
1092 $('.searchTag.searchTagIcon').html('<i class="icon-spin animate-spin"></i>')
1089 $('.searchTag.searchTagIcon').html('<i class="icon-spin animate-spin"></i>')
1093 },
1090 },
1094 onSearchComplete: function (query, suggestions) {
1091 onSearchComplete: function (query, suggestions) {
1095 $('.searchTag.searchTagIcon').html('<i class="icon-search"></i>')
1092 $('.searchTag.searchTagIcon').html('<i class="icon-search"></i>')
1096 },
1093 },
1097 });
1094 });
1098
1095
1099 showMainFilterBox = function () {
1096 showMainFilterBox = function () {
1100 $('#main_filter_help').toggle();
1097 $('#main_filter_help').toggle();
1101 };
1098 };
1102
1099
1103 $('#main_filter').on('keydown.autocomplete', function (e) {
1100 $('#main_filter').on('keydown.autocomplete', function (e) {
1104
1101
1105 var BACKSPACE = 8;
1102 var BACKSPACE = 8;
1106 var el = $(e.currentTarget);
1103 var el = $(e.currentTarget);
1107 if(e.which === BACKSPACE){
1104 if(e.which === BACKSPACE){
1108 var inputVal = el.val();
1105 var inputVal = el.val();
1109 if (inputVal === ""){
1106 if (inputVal === ""){
1110 removeGoToFilter()
1107 removeGoToFilter()
1111 }
1108 }
1112 }
1109 }
1113 });
1110 });
1114
1111
1115 var dismissNotice = function(noticeId) {
1112 var dismissNotice = function(noticeId) {
1116
1113
1117 var url = pyroutes.url('user_notice_dismiss',
1114 var url = pyroutes.url('user_notice_dismiss',
1118 {"user_id": templateContext.rhodecode_user.user_id});
1115 {"user_id": templateContext.rhodecode_user.user_id});
1119
1116
1120 var postData = {
1117 var postData = {
1121 'csrf_token': CSRF_TOKEN,
1118 'csrf_token': CSRF_TOKEN,
1122 'notice_id': noticeId,
1119 'notice_id': noticeId,
1123 };
1120 };
1124
1121
1125 var success = function(response) {
1122 var success = function(response) {
1126 $('#notice-message-' + noticeId).remove();
1123 $('#notice-message-' + noticeId).remove();
1127 return false;
1124 return false;
1128 };
1125 };
1129 var failure = function(data, textStatus, xhr) {
1126 var failure = function(data, textStatus, xhr) {
1130 alert("error processing request: " + textStatus);
1127 alert("error processing request: " + textStatus);
1131 return false;
1128 return false;
1132 };
1129 };
1133 ajaxPOST(url, postData, success, failure);
1130 ajaxPOST(url, postData, success, failure);
1134 }
1131 }
1135
1132
1136 var hideLicenseWarning = function () {
1133 var hideLicenseWarning = function () {
1137 var fingerprint = templateContext.session_attrs.license_fingerprint;
1134 var fingerprint = templateContext.session_attrs.license_fingerprint;
1138 storeUserSessionAttr('rc_user_session_attr.hide_license_warning', fingerprint);
1135 storeUserSessionAttr('rc_user_session_attr.hide_license_warning', fingerprint);
1139 $('#notifications').hide();
1136 $('#notifications').hide();
1140 }
1137 }
1141
1138
1142 var hideLicenseError = function () {
1139 var hideLicenseError = function () {
1143 var fingerprint = templateContext.session_attrs.license_fingerprint;
1140 var fingerprint = templateContext.session_attrs.license_fingerprint;
1144 storeUserSessionAttr('rc_user_session_attr.hide_license_error', fingerprint);
1141 storeUserSessionAttr('rc_user_session_attr.hide_license_error', fingerprint);
1145 $('#notifications').hide();
1142 $('#notifications').hide();
1146 }
1143 }
1147
1144
1148 </script>
1145 </script>
1149 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1146 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
1150 </%def>
1147 </%def>
1151
1148
1152 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1149 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
1153 <div class="modal-dialog">
1150 <div class="modal-dialog">
1154 <div class="modal-content">
1151 <div class="modal-content">
1155 <div class="modal-header">
1152 <div class="modal-header">
1156 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
1153 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
1157 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
1154 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
1158 </div>
1155 </div>
1159 <div class="modal-body">
1156 <div class="modal-body">
1160 <div class="block-left">
1157 <div class="block-left">
1161 <table class="keyboard-mappings">
1158 <table class="keyboard-mappings">
1162 <tbody>
1159 <tbody>
1163 <tr>
1160 <tr>
1164 <th></th>
1161 <th></th>
1165 <th>${_('Site-wide shortcuts')}</th>
1162 <th>${_('Site-wide shortcuts')}</th>
1166 </tr>
1163 </tr>
1167 <%
1164 <%
1168 elems = [
1165 elems = [
1169 ('/', 'Use quick search box'),
1166 ('/', 'Use quick search box'),
1170 ('g h', 'Goto home page'),
1167 ('g h', 'Goto home page'),
1171 ('g g', 'Goto my private gists page'),
1168 ('g g', 'Goto my private gists page'),
1172 ('g G', 'Goto my public gists page'),
1169 ('g G', 'Goto my public gists page'),
1173 ('g 0-9', 'Goto bookmarked items from 0-9'),
1170 ('g 0-9', 'Goto bookmarked items from 0-9'),
1174 ('n r', 'New repository page'),
1171 ('n r', 'New repository page'),
1175 ('n g', 'New gist page'),
1172 ('n g', 'New gist page'),
1176 ]
1173 ]
1177 %>
1174 %>
1178 %for key, desc in elems:
1175 %for key, desc in elems:
1179 <tr>
1176 <tr>
1180 <td class="keys">
1177 <td class="keys">
1181 <span class="key tag">${key}</span>
1178 <span class="key tag">${key}</span>
1182 </td>
1179 </td>
1183 <td>${desc}</td>
1180 <td>${desc}</td>
1184 </tr>
1181 </tr>
1185 %endfor
1182 %endfor
1186 </tbody>
1183 </tbody>
1187 </table>
1184 </table>
1188 </div>
1185 </div>
1189 <div class="block-left">
1186 <div class="block-left">
1190 <table class="keyboard-mappings">
1187 <table class="keyboard-mappings">
1191 <tbody>
1188 <tbody>
1192 <tr>
1189 <tr>
1193 <th></th>
1190 <th></th>
1194 <th>${_('Repositories')}</th>
1191 <th>${_('Repositories')}</th>
1195 </tr>
1192 </tr>
1196 <%
1193 <%
1197 elems = [
1194 elems = [
1198 ('g s', 'Goto summary page'),
1195 ('g s', 'Goto summary page'),
1199 ('g c', 'Goto changelog page'),
1196 ('g c', 'Goto changelog page'),
1200 ('g f', 'Goto files page'),
1197 ('g f', 'Goto files page'),
1201 ('g F', 'Goto files page with file search activated'),
1198 ('g F', 'Goto files page with file search activated'),
1202 ('g p', 'Goto pull requests page'),
1199 ('g p', 'Goto pull requests page'),
1203 ('g o', 'Goto repository settings'),
1200 ('g o', 'Goto repository settings'),
1204 ('g O', 'Goto repository access permissions settings'),
1201 ('g O', 'Goto repository access permissions settings'),
1202 ('t s', 'Toggle sidebar on some pages'),
1205 ]
1203 ]
1206 %>
1204 %>
1207 %for key, desc in elems:
1205 %for key, desc in elems:
1208 <tr>
1206 <tr>
1209 <td class="keys">
1207 <td class="keys">
1210 <span class="key tag">${key}</span>
1208 <span class="key tag">${key}</span>
1211 </td>
1209 </td>
1212 <td>${desc}</td>
1210 <td>${desc}</td>
1213 </tr>
1211 </tr>
1214 %endfor
1212 %endfor
1215 </tbody>
1213 </tbody>
1216 </table>
1214 </table>
1217 </div>
1215 </div>
1218 </div>
1216 </div>
1219 <div class="modal-footer">
1217 <div class="modal-footer">
1220 </div>
1218 </div>
1221 </div><!-- /.modal-content -->
1219 </div><!-- /.modal-content -->
1222 </div><!-- /.modal-dialog -->
1220 </div><!-- /.modal-dialog -->
1223 </div><!-- /.modal -->
1221 </div><!-- /.modal -->
1222
1223
1224 <script type="text/javascript">
1225 (function () {
1226 "use sctrict";
1227
1228 var $sideBar = $('.right-sidebar');
1229 var expanded = $sideBar.hasClass('right-sidebar-expanded');
1230 var sidebarState = templateContext.session_attrs.sidebarState;
1231 var sidebarEnabled = $('aside.right-sidebar').get(0);
1232
1233 if (sidebarState === 'expanded') {
1234 expanded = true
1235 } else if (sidebarState === 'collapsed') {
1236 expanded = false
1237 }
1238 if (sidebarEnabled) {
1239 // show sidebar since it's hidden on load
1240 $('.right-sidebar').show();
1241
1242 // init based on set initial class, or if defined user session attrs
1243 if (expanded) {
1244 window.expandSidebar();
1245 window.updateStickyHeader();
1246
1247 } else {
1248 window.collapseSidebar();
1249 window.updateStickyHeader();
1250 }
1251 }
1252 })()
1253
1254 </script>
@@ -1,302 +1,431 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.mako"/>
3 <%inherit file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
4 <%namespace name="base" file="/base/base.mako"/>
5 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
5 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
6 <%namespace name="file_base" file="/files/base.mako"/>
6 <%namespace name="file_base" file="/files/base.mako"/>
7 <%namespace name="sidebar" file="/base/sidebar.mako"/>
8
7
9
8 <%def name="title()">
10 <%def name="title()">
9 ${_('{} Commit').format(c.repo_name)} - ${h.show_id(c.commit)}
11 ${_('{} Commit').format(c.repo_name)} - ${h.show_id(c.commit)}
10 %if c.rhodecode_name:
12 %if c.rhodecode_name:
11 &middot; ${h.branding(c.rhodecode_name)}
13 &middot; ${h.branding(c.rhodecode_name)}
12 %endif
14 %endif
13 </%def>
15 </%def>
14
16
15 <%def name="menu_bar_nav()">
17 <%def name="menu_bar_nav()">
16 ${self.menu_items(active='repositories')}
18 ${self.menu_items(active='repositories')}
17 </%def>
19 </%def>
18
20
19 <%def name="menu_bar_subnav()">
21 <%def name="menu_bar_subnav()">
20 ${self.repo_menu(active='commits')}
22 ${self.repo_menu(active='commits')}
21 </%def>
23 </%def>
22
24
23 <%def name="main()">
25 <%def name="main()">
24 <script type="text/javascript">
26 <script type="text/javascript">
25 // TODO: marcink switch this to pyroutes
27 // TODO: marcink switch this to pyroutes
26 AJAX_COMMENT_DELETE_URL = "${h.route_path('repo_commit_comment_delete',repo_name=c.repo_name,commit_id=c.commit.raw_id,comment_id='__COMMENT_ID__')}";
28 AJAX_COMMENT_DELETE_URL = "${h.route_path('repo_commit_comment_delete',repo_name=c.repo_name,commit_id=c.commit.raw_id,comment_id='__COMMENT_ID__')}";
27 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
29 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
28 </script>
30 </script>
29
31
30 <div class="box">
32 <div class="box">
31
33
32 <div class="summary">
34 <div class="summary">
33
35
34 <div class="fieldset">
36 <div class="fieldset">
35 <div class="left-content">
37 <div class="left-content">
36 <%
38 <%
37 rc_user = h.discover_user(c.commit.author_email)
39 rc_user = h.discover_user(c.commit.author_email)
38 %>
40 %>
39 <div class="left-content-avatar">
41 <div class="left-content-avatar">
40 ${base.gravatar(c.commit.author_email, 30, tooltip=(True if rc_user else False), user=rc_user)}
42 ${base.gravatar(c.commit.author_email, 30, tooltip=(True if rc_user else False), user=rc_user)}
41 </div>
43 </div>
42
44
43 <div class="left-content-message">
45 <div class="left-content-message">
44 <div class="fieldset collapsable-content no-hide" data-toggle="summary-details">
46 <div class="fieldset collapsable-content no-hide" data-toggle="summary-details">
45 <div class="commit truncate-wrap">${h.urlify_commit_message(h.chop_at_smart(c.commit.message, '\n', suffix_if_chopped='...'), c.repo_name)}</div>
47 <div class="commit truncate-wrap">${h.urlify_commit_message(h.chop_at_smart(c.commit.message, '\n', suffix_if_chopped='...'), c.repo_name)}</div>
46 </div>
48 </div>
47
49
48 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none">
50 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none">
49 <div class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
51 <div class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
50 </div>
52 </div>
51
53
52 <div class="fieldset" data-toggle="summary-details">
54 <div class="fieldset" data-toggle="summary-details">
53 <div class="">
55 <div class="">
54 <table>
56 <table>
55 <tr class="file_author">
57 <tr class="file_author">
56
58
57 <td>
59 <td>
58 <span class="user commit-author">${h.link_to_user(rc_user or c.commit.author)}</span>
60 <span class="user commit-author">${h.link_to_user(rc_user or c.commit.author)}</span>
59 <span class="commit-date">- ${h.age_component(c.commit.date)}</span>
61 <span class="commit-date">- ${h.age_component(c.commit.date)}</span>
60 </td>
62 </td>
61
63
62 <td>
64 <td>
63 ## second cell for consistency with files
65 ## second cell for consistency with files
64 </td>
66 </td>
65 </tr>
67 </tr>
66 </table>
68 </table>
67 </div>
69 </div>
68 </div>
70 </div>
69
71
70 </div>
72 </div>
71 </div>
73 </div>
72
74
73 <div class="right-content">
75 <div class="right-content">
74
76
75 <div data-toggle="summary-details">
77 <div data-toggle="summary-details">
76 <div class="tags tags-main">
78 <div class="tags tags-main">
77 <code><a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">${h.show_id(c.commit)}</a></code>
79 <code><a href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">${h.show_id(c.commit)}</a></code>
78 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
80 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${c.commit.raw_id}" title="${_('Copy the full commit id')}"></i>
79 ${file_base.refs(c.commit)}
81 ${file_base.refs(c.commit)}
80
82
81 ## phase
83 ## phase
82 % if hasattr(c.commit, 'phase') and getattr(c.commit, 'phase') != 'public':
84 % if hasattr(c.commit, 'phase') and getattr(c.commit, 'phase') != 'public':
83 <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">
85 <span class="tag phase-${c.commit.phase} tooltip" title="${_('Commit phase')}">
84 <i class="icon-info"></i>${c.commit.phase}
86 <i class="icon-info"></i>${c.commit.phase}
85 </span>
87 </span>
86 % endif
88 % endif
87
89
88 ## obsolete commits
90 ## obsolete commits
89 % if getattr(c.commit, 'obsolete', False):
91 % if getattr(c.commit, 'obsolete', False):
90 <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">
92 <span class="tag obsolete-${c.commit.obsolete} tooltip" title="${_('Evolve State')}">
91 ${_('obsolete')}
93 ${_('obsolete')}
92 </span>
94 </span>
93 % endif
95 % endif
94
96
95 ## hidden commits
97 ## hidden commits
96 % if getattr(c.commit, 'hidden', False):
98 % if getattr(c.commit, 'hidden', False):
97 <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">
99 <span class="tag hidden-${c.commit.hidden} tooltip" title="${_('Evolve State')}">
98 ${_('hidden')}
100 ${_('hidden')}
99 </span>
101 </span>
100 % endif
102 % endif
101 </div>
103 </div>
102
104
103 %if c.statuses:
104 <div class="tag status-tag-${c.statuses[0]} pull-right">
105 <i class="icon-circle review-status-${c.statuses[0]}"></i>
106 <div class="pull-right">${h.commit_status_lbl(c.statuses[0])}</div>
107 </div>
108 %endif
109
110 </div>
111
112 </div>
113 </div>
114
115 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
116 <div class="left-label-summary">
117 <p>${_('Commit navigation')}:</p>
118 <div class="right-label-summary">
119 <span id="parent_link" class="tag tagtag">
105 <span id="parent_link" class="tag tagtag">
120 <a href="#parentCommit" title="${_('Parent Commit')}"><i class="icon-left icon-no-margin"></i>${_('parent')}</a>
106 <a href="#parentCommit" title="${_('Parent Commit')}"><i class="icon-left icon-no-margin"></i>${_('parent')}</a>
121 </span>
107 </span>
122
108
123 <span id="child_link" class="tag tagtag">
109 <span id="child_link" class="tag tagtag">
124 <a href="#childCommit" title="${_('Child Commit')}">${_('child')}<i class="icon-right icon-no-margin"></i></a>
110 <a href="#childCommit" title="${_('Child Commit')}">${_('child')}<i class="icon-right icon-no-margin"></i></a>
125 </span>
111 </span>
112
126 </div>
113 </div>
114
127 </div>
115 </div>
128 </div>
116 </div>
129
117
130 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
118 <div class="fieldset collapsable-content" data-toggle="summary-details" style="display: none;">
131 <div class="left-label-summary">
119 <div class="left-label-summary">
132 <p>${_('Diff options')}:</p>
120 <p>${_('Diff options')}:</p>
133 <div class="right-label-summary">
121 <div class="right-label-summary">
134 <div class="diff-actions">
122 <div class="diff-actions">
135 <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">
123 <a href="${h.route_path('repo_commit_raw',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">
136 ${_('Raw Diff')}
124 ${_('Raw Diff')}
137 </a>
125 </a>
138 |
126 |
139 <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">
127 <a href="${h.route_path('repo_commit_patch',repo_name=c.repo_name,commit_id=c.commit.raw_id)}">
140 ${_('Patch Diff')}
128 ${_('Patch Diff')}
141 </a>
129 </a>
142 |
130 |
143 <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(diff='download'))}">
131 <a href="${h.route_path('repo_commit_download',repo_name=c.repo_name,commit_id=c.commit.raw_id,_query=dict(diff='download'))}">
144 ${_('Download Diff')}
132 ${_('Download Diff')}
145 </a>
133 </a>
146 </div>
134 </div>
147 </div>
135 </div>
148 </div>
136 </div>
149 </div>
137 </div>
150
138
151 <div class="clear-fix"></div>
139 <div class="clear-fix"></div>
152
140
153 <div class="btn-collapse" data-toggle="summary-details">
141 <div class="btn-collapse" data-toggle="summary-details">
154 ${_('Show More')}
142 ${_('Show More')}
155 </div>
143 </div>
156
144
157 </div>
145 </div>
158
146
159 <div class="cs_files">
147 <div class="cs_files">
160 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
148 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
161 ${cbdiffs.render_diffset_menu(c.changes[c.commit.raw_id], commit=c.commit)}
149 ${cbdiffs.render_diffset_menu(c.changes[c.commit.raw_id], commit=c.commit)}
162 ${cbdiffs.render_diffset(
150 ${cbdiffs.render_diffset(
163 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True,inline_comments=c.inline_comments )}
151 c.changes[c.commit.raw_id], commit=c.commit, use_comments=True,
152 inline_comments=c.inline_comments,
153 show_todos=False)}
164 </div>
154 </div>
165
155
166 ## template for inline comment form
156 ## template for inline comment form
167 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
157 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
168
158
169 ## comments heading with count
159 ## comments heading with count
170 <div class="comments-heading">
160 <div class="comments-heading">
171 <i class="icon-comment"></i>
161 <i class="icon-comment"></i>
172 ${_('Comments')} ${len(c.comments)}
162 ${_('General Comments')} ${len(c.comments)}
173 </div>
163 </div>
174
164
175 ## render comments
165 ## render comments
176 ${comment.generate_comments(c.comments)}
166 ${comment.generate_comments(c.comments)}
177
167
178 ## main comment form and it status
168 ## main comment form and it status
179 ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id=c.commit.raw_id),
169 ${comment.comments(h.route_path('repo_commit_comment_create', repo_name=c.repo_name, commit_id=c.commit.raw_id),
180 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
170 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
181 </div>
171 </div>
182
172
183 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
173 ### NAV SIDEBAR
184 <script type="text/javascript">
174 <aside class="right-sidebar right-sidebar-expanded" id="commit-nav-sticky" style="display: none">
175 <div class="sidenav navbar__inner" >
176 ## TOGGLE
177 <div class="sidebar-toggle" onclick="toggleSidebar(); return false">
178 <a href="#toggleSidebar" class="grey-link-action">
179
180 </a>
181 </div>
182
183 ## CONTENT
184 <div class="sidebar-content">
185
185
186 $(document).ready(function() {
186 ## RULES SUMMARY/RULES
187 <div class="sidebar-element clear-both">
188 <% vote_title = _ungettext(
189 'Status calculated based on votes from {} reviewer',
190 'Status calculated based on votes from {} reviewers', len(c.allowed_reviewers)).format(len(c.allowed_reviewers))
191 %>
192
193 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${vote_title}">
194 <i class="icon-circle review-status-${c.commit_review_status}"></i>
195 ${len(c.allowed_reviewers)}
196 </div>
197 </div>
187
198
188 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
199 ## REVIEWERS
189 if($('#trimmed_message_box').height() === boxmax){
200 <div class="right-sidebar-expanded-state pr-details-title">
190 $('#message_expand').show();
201 <span class="tooltip sidebar-heading" title="${vote_title}">
191 }
202 <i class="icon-circle review-status-${c.commit_review_status}"></i>
203 ${_('Reviewers')}
204 </span>
205 </div>
206
207 <div id="reviewers" class="right-sidebar-expanded-state pr-details-content reviewers">
208
209 <table id="review_members" class="group_members">
210 ## This content is loaded via JS and ReviewersPanel
211 </table>
212
213 </div>
214
215 ## TODOs
216 <div class="sidebar-element clear-both">
217 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="TODOs">
218 <i class="icon-flag-filled"></i>
219 <span id="todos-count">${len(c.unresolved_comments)}</span>
220 </div>
221
222 <div class="right-sidebar-expanded-state pr-details-title">
223 ## Only show unresolved, that is only what matters
224 <span class="sidebar-heading noselect" onclick="refreshTODOs(); return false">
225 <i class="icon-flag-filled"></i>
226 TODOs
227 </span>
228
229 % if c.resolved_comments:
230 <span class="block-right action_button last-item noselect" onclick="$('.unresolved-todo-text').toggle(); return toggleElement(this, '.resolved-todo');" data-toggle-on="Show resolved" data-toggle-off="Hide resolved">Show resolved</span>
231 % else:
232 <span class="block-right last-item noselect">Show resolved</span>
233 % endif
234
235 </div>
192
236
193 $('#message_expand').on('click', function(e){
237 <div class="right-sidebar-expanded-state pr-details-content">
194 $('#trimmed_message_box').css('max-height', 'none');
238 % if c.unresolved_comments + c.resolved_comments:
195 $(this).hide();
239 ${sidebar.comments_table(c.unresolved_comments + c.resolved_comments, len(c.unresolved_comments), todo_comments=True, is_pr=False)}
196 });
240 % else:
241 <table>
242 <tr>
243 <td>
244 ${_('No TODOs yet')}
245 </td>
246 </tr>
247 </table>
248 % endif
249 </div>
250 </div>
251
252 ## COMMENTS
253 <div class="sidebar-element clear-both">
254 <div class="tooltip right-sidebar-collapsed-state" style="display: none" onclick="toggleSidebar(); return false" title="${_('Comments')}">
255 <i class="icon-comment" style="color: #949494"></i>
256 <span id="comments-count">${len(c.inline_comments_flat+c.comments)}</span>
257 <span class="display-none" id="general-comments-count">${len(c.comments)}</span>
258 <span class="display-none" id="inline-comments-count">${len(c.inline_comments_flat)}</span>
259 </div>
260
261 <div class="right-sidebar-expanded-state pr-details-title">
262 <span class="sidebar-heading noselect" onclick="refreshComments(); return false">
263 <i class="icon-comment" style="color: #949494"></i>
264 ${_('Comments')}
265 </span>
266
267 </div>
197
268
198 $('.show-inline-comments').on('click', function(e){
269 <div class="right-sidebar-expanded-state pr-details-content">
199 var boxid = $(this).attr('data-comment-id');
270 % if c.inline_comments_flat + c.comments:
200 var button = $(this);
271 ${sidebar.comments_table(c.inline_comments_flat + c.comments, len(c.inline_comments_flat+c.comments), is_pr=False)}
272 % else:
273 <table>
274 <tr>
275 <td>
276 ${_('No Comments yet')}
277 </td>
278 </tr>
279 </table>
280 % endif
281 </div>
282
283 </div>
284
285 </div>
286
287 </div>
288 </aside>
201
289
202 if(button.hasClass("comments-visible")) {
290 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
203 $('#{0} .inline-comments'.format(boxid)).each(function(index){
291 <script type="text/javascript">
204 $(this).hide();
292 window.setReviewersData = ${c.commit_set_reviewers_data_json | n};
293
294 $(document).ready(function () {
295 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
296
297 if ($('#trimmed_message_box').height() === boxmax) {
298 $('#message_expand').show();
299 }
300
301 $('#message_expand').on('click', function (e) {
302 $('#trimmed_message_box').css('max-height', 'none');
303 $(this).hide();
304 });
305
306 $('.show-inline-comments').on('click', function (e) {
307 var boxid = $(this).attr('data-comment-id');
308 var button = $(this);
309
310 if (button.hasClass("comments-visible")) {
311 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
312 $(this).hide();
205 });
313 });
206 button.removeClass("comments-visible");
314 button.removeClass("comments-visible");
207 } else {
315 } else {
208 $('#{0} .inline-comments'.format(boxid)).each(function(index){
316 $('#{0} .inline-comments'.format(boxid)).each(function (index) {
209 $(this).show();
317 $(this).show();
210 });
318 });
211 button.addClass("comments-visible");
319 button.addClass("comments-visible");
212 }
320 }
213 });
321 });
214
322
215 // next links
323 // next links
216 $('#child_link').on('click', function(e){
324 $('#child_link').on('click', function (e) {
217 // fetch via ajax what is going to be the next link, if we have
325 // fetch via ajax what is going to be the next link, if we have
218 // >1 links show them to user to choose
326 // >1 links show them to user to choose
219 if(!$('#child_link').hasClass('disabled')){
327 if (!$('#child_link').hasClass('disabled')) {
220 $.ajax({
328 $.ajax({
221 url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
329 url: '${h.route_path('repo_commit_children',repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
222 success: function(data) {
330 success: function (data) {
223 if(data.results.length === 0){
331 if (data.results.length === 0) {
224 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
332 $('#child_link').html("${_('No Child Commits')}").addClass('disabled');
225 }
333 }
226 if(data.results.length === 1){
334 if (data.results.length === 1) {
227 var commit = data.results[0];
335 var commit = data.results[0];
228 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
336 window.location = pyroutes.url('repo_commit', {
229 }
337 'repo_name': '${c.repo_name}',
230 else if(data.results.length === 2){
338 'commit_id': commit.raw_id
231 $('#child_link').addClass('disabled');
339 });
232 $('#child_link').addClass('double');
340 } else if (data.results.length === 2) {
341 $('#child_link').addClass('disabled');
342 $('#child_link').addClass('double');
233
343
234 var _html = '';
344 var _html = '';
235 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
345 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
236 .replace('__branch__', data.results[0].branch)
346 .replace('__branch__', data.results[0].branch)
237 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
347 .replace('__rev__', 'r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0, 6)))
238 .replace('__title__', data.results[0].message)
348 .replace('__title__', data.results[0].message)
239 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
349 .replace('__url__', pyroutes.url('repo_commit', {
240 _html +=' | ';
350 'repo_name': '${c.repo_name}',
241 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
351 'commit_id': data.results[0].raw_id
242 .replace('__branch__', data.results[1].branch)
352 }));
243 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
353 _html += ' | ';
244 .replace('__title__', data.results[1].message)
354 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a> '
245 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
355 .replace('__branch__', data.results[1].branch)
246 $('#child_link').html(_html);
356 .replace('__rev__', 'r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0, 6)))
247 }
357 .replace('__title__', data.results[1].message)
358 .replace('__url__', pyroutes.url('repo_commit', {
359 'repo_name': '${c.repo_name}',
360 'commit_id': data.results[1].raw_id
361 }));
362 $('#child_link').html(_html);
363 }
248 }
364 }
249 });
365 });
250 e.preventDefault();
366 e.preventDefault();
251 }
367 }
252 });
368 });
253
369
254 // prev links
370 // prev links
255 $('#parent_link').on('click', function(e){
371 $('#parent_link').on('click', function (e) {
256 // fetch via ajax what is going to be the next link, if we have
372 // fetch via ajax what is going to be the next link, if we have
257 // >1 links show them to user to choose
373 // >1 links show them to user to choose
258 if(!$('#parent_link').hasClass('disabled')){
374 if (!$('#parent_link').hasClass('disabled')) {
259 $.ajax({
375 $.ajax({
260 url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
376 url: '${h.route_path("repo_commit_parents",repo_name=c.repo_name, commit_id=c.commit.raw_id)}',
261 success: function(data) {
377 success: function (data) {
262 if(data.results.length === 0){
378 if (data.results.length === 0) {
263 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
379 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
264 }
380 }
265 if(data.results.length === 1){
381 if (data.results.length === 1) {
266 var commit = data.results[0];
382 var commit = data.results[0];
267 window.location = pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': commit.raw_id});
383 window.location = pyroutes.url('repo_commit', {
268 }
384 'repo_name': '${c.repo_name}',
269 else if(data.results.length === 2){
385 'commit_id': commit.raw_id
270 $('#parent_link').addClass('disabled');
386 });
271 $('#parent_link').addClass('double');
387 } else if (data.results.length === 2) {
388 $('#parent_link').addClass('disabled');
389 $('#parent_link').addClass('double');
272
390
273 var _html = '';
391 var _html = '';
274 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
392 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
275 .replace('__branch__', data.results[0].branch)
393 .replace('__branch__', data.results[0].branch)
276 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
394 .replace('__rev__', 'r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0, 6)))
277 .replace('__title__', data.results[0].message)
395 .replace('__title__', data.results[0].message)
278 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[0].raw_id}));
396 .replace('__url__', pyroutes.url('repo_commit', {
279 _html +=' | ';
397 'repo_name': '${c.repo_name}',
280 _html +='<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
398 'commit_id': data.results[0].raw_id
281 .replace('__branch__', data.results[1].branch)
399 }));
282 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
400 _html += ' | ';
283 .replace('__title__', data.results[1].message)
401 _html += '<a title="__title__" href="__url__"><span class="tag branchtag"><i class="icon-code-fork"></i>__branch__</span> __rev__</a>'
284 .replace('__url__', pyroutes.url('repo_commit', {'repo_name': '${c.repo_name}','commit_id': data.results[1].raw_id}));
402 .replace('__branch__', data.results[1].branch)
285 $('#parent_link').html(_html);
403 .replace('__rev__', 'r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0, 6)))
286 }
404 .replace('__title__', data.results[1].message)
405 .replace('__url__', pyroutes.url('repo_commit', {
406 'repo_name': '${c.repo_name}',
407 'commit_id': data.results[1].raw_id
408 }));
409 $('#parent_link').html(_html);
410 }
287 }
411 }
288 });
412 });
289 e.preventDefault();
413 e.preventDefault();
290 }
414 }
291 });
415 });
292
416
293 // browse tree @ revision
417 // browse tree @ revision
294 $('#files_link').on('click', function(e){
418 $('#files_link').on('click', function (e) {
295 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
419 window.location = '${h.route_path('repo_files:default_path',repo_name=c.repo_name, commit_id=c.commit.raw_id)}';
296 e.preventDefault();
420 e.preventDefault();
297 });
421 });
298
422
299 })
423 ReviewersPanel.init(null, setReviewersData);
300 </script>
424
425 var channel = '${c.commit_broadcast_channel}';
426 new ReviewerPresenceController(channel)
427
428 })
429 </script>
301
430
302 </%def>
431 </%def>
@@ -1,493 +1,518 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ## usage:
2 ## usage:
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
3 ## <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
4 ## ${comment.comment_block(comment)}
4 ## ${comment.comment_block(comment)}
5 ##
5 ##
6
6
7 <%!
7 <%!
8 from rhodecode.lib import html_filters
8 from rhodecode.lib import html_filters
9 %>
9 %>
10
10
11 <%namespace name="base" file="/base/base.mako"/>
11 <%namespace name="base" file="/base/base.mako"/>
12 <%def name="comment_block(comment, inline=False, active_pattern_entries=None)">
12 <%def name="comment_block(comment, inline=False, active_pattern_entries=None)">
13
13
14 <%
15 from rhodecode.model.comment import CommentsModel
16 comment_model = CommentsModel()
17 %>
14 <% comment_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
18 <% comment_ver = comment.get_index_version(getattr(c, 'versions', [])) %>
15 <% latest_ver = len(getattr(c, 'versions', [])) %>
19 <% latest_ver = len(getattr(c, 'versions', [])) %>
16
20
17 % if inline:
21 % if inline:
18 <% outdated_at_ver = comment.outdated_at_version(c.at_version_num) %>
22 <% outdated_at_ver = comment.outdated_at_version(c.at_version_num) %>
19 % else:
23 % else:
20 <% outdated_at_ver = comment.older_than_version(c.at_version_num) %>
24 <% outdated_at_ver = comment.older_than_version(c.at_version_num) %>
21 % endif
25 % endif
22
26
23 <div class="comment
27 <div class="comment
24 ${'comment-inline' if inline else 'comment-general'}
28 ${'comment-inline' if inline else 'comment-general'}
25 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
29 ${'comment-outdated' if outdated_at_ver else 'comment-current'}"
26 id="comment-${comment.comment_id}"
30 id="comment-${comment.comment_id}"
27 line="${comment.line_no}"
31 line="${comment.line_no}"
28 data-comment-id="${comment.comment_id}"
32 data-comment-id="${comment.comment_id}"
29 data-comment-type="${comment.comment_type}"
33 data-comment-type="${comment.comment_type}"
30 data-comment-renderer="${comment.renderer}"
34 data-comment-renderer="${comment.renderer}"
31 data-comment-text="${comment.text | html_filters.base64,n}"
35 data-comment-text="${comment.text | html_filters.base64,n}"
32 data-comment-line-no="${comment.line_no}"
36 data-comment-line-no="${comment.line_no}"
33 data-comment-inline=${h.json.dumps(inline)}
37 data-comment-inline=${h.json.dumps(inline)}
34 style="${'display: none;' if outdated_at_ver else ''}">
38 style="${'display: none;' if outdated_at_ver else ''}">
35
39
36 <div class="meta">
40 <div class="meta">
37 <div class="comment-type-label">
41 <div class="comment-type-label">
38 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}">
42 <div class="comment-label ${comment.comment_type or 'note'}" id="comment-label-${comment.comment_id}">
39
43
40 ## TODO COMMENT
44 ## TODO COMMENT
41 % if comment.comment_type == 'todo':
45 % if comment.comment_type == 'todo':
42 % if comment.resolved:
46 % if comment.resolved:
43 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
47 <div class="resolved tooltip" title="${_('Resolved by comment #{}').format(comment.resolved.comment_id)}">
44 <i class="icon-flag-filled"></i>
48 <i class="icon-flag-filled"></i>
45 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
49 <a href="#comment-${comment.resolved.comment_id}">${comment.comment_type}</a>
46 </div>
50 </div>
47 % else:
51 % else:
48 <div class="resolved tooltip" style="display: none">
52 <div class="resolved tooltip" style="display: none">
49 <span>${comment.comment_type}</span>
53 <span>${comment.comment_type}</span>
50 </div>
54 </div>
51 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to create resolution comment.')}">
55 <div class="resolve tooltip" onclick="return Rhodecode.comments.createResolutionComment(${comment.comment_id});" title="${_('Click to create resolution comment.')}">
52 <i class="icon-flag-filled"></i>
56 <i class="icon-flag-filled"></i>
53 ${comment.comment_type}
57 ${comment.comment_type}
54 </div>
58 </div>
55 % endif
59 % endif
56 ## NOTE COMMENT
60 ## NOTE COMMENT
57 % else:
61 % else:
58 ## RESOLVED NOTE
62 ## RESOLVED NOTE
59 % if comment.resolved_comment:
63 % if comment.resolved_comment:
60 <div class="tooltip" title="${_('This comment resolves TODO #{}').format(comment.resolved_comment.comment_id)}">
64 <div class="tooltip" title="${_('This comment resolves TODO #{}').format(comment.resolved_comment.comment_id)}">
61 fix
65 fix
62 <a href="#comment-${comment.resolved_comment.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${comment.resolved_comment.comment_id}'), 0, ${h.json.dumps(comment.resolved_comment.outdated)})">
66 <a href="#comment-${comment.resolved_comment.comment_id}" onclick="Rhodecode.comments.scrollToComment($('#comment-${comment.resolved_comment.comment_id}'), 0, ${h.json.dumps(comment.resolved_comment.outdated)})">
63 <span style="text-decoration: line-through">#${comment.resolved_comment.comment_id}</span>
67 <span style="text-decoration: line-through">#${comment.resolved_comment.comment_id}</span>
64 </a>
68 </a>
65 </div>
69 </div>
66 ## STATUS CHANGE NOTE
70 ## STATUS CHANGE NOTE
67 % elif not comment.is_inline and comment.status_change:
71 % elif not comment.is_inline and comment.status_change:
68 <%
72 <%
69 if comment.pull_request:
73 if comment.pull_request:
70 status_change_title = 'Status of review for pull request !{}'.format(comment.pull_request.pull_request_id)
74 status_change_title = 'Status of review for pull request !{}'.format(comment.pull_request.pull_request_id)
71 else:
75 else:
72 status_change_title = 'Status of review for commit {}'.format(h.short_id(comment.commit_id))
76 status_change_title = 'Status of review for commit {}'.format(h.short_id(comment.commit_id))
73 %>
77 %>
74
78
75 <i class="icon-circle review-status-${comment.status_change[0].status}"></i>
79 <i class="icon-circle review-status-${comment.status_change[0].status}"></i>
76 <div class="changeset-status-lbl tooltip" title="${status_change_title}">
80 <div class="changeset-status-lbl tooltip" title="${status_change_title}">
77 ${comment.status_change[0].status_lbl}
81 ${comment.status_change[0].status_lbl}
78 </div>
82 </div>
79 % else:
83 % else:
80 <div>
84 <div>
81 <i class="icon-comment"></i>
85 <i class="icon-comment"></i>
82 ${(comment.comment_type or 'note')}
86 ${(comment.comment_type or 'note')}
83 </div>
87 </div>
84 % endif
88 % endif
85 % endif
89 % endif
86
90
87 </div>
91 </div>
88 </div>
92 </div>
89
93
90 % if 0 and comment.status_change:
94 % if 0 and comment.status_change:
91 <div class="pull-left">
95 <div class="pull-left">
92 <span class="tag authortag tooltip" title="${_('Status from pull request.')}">
96 <span class="tag authortag tooltip" title="${_('Status from pull request.')}">
93 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
97 <a href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id)}">
94 ${'!{}'.format(comment.pull_request.pull_request_id)}
98 ${'!{}'.format(comment.pull_request.pull_request_id)}
95 </a>
99 </a>
96 </span>
100 </span>
97 </div>
101 </div>
98 % endif
102 % endif
99
103
100 <div class="author ${'author-inline' if inline else 'author-general'}">
104 <div class="author ${'author-inline' if inline else 'author-general'}">
101 ${base.gravatar_with_user(comment.author.email, 16, tooltip=True)}
105 ${base.gravatar_with_user(comment.author.email, 16, tooltip=True)}
102 </div>
106 </div>
103
107
104 <div class="date">
108 <div class="date">
105 ${h.age_component(comment.modified_at, time_is_local=True)}
109 ${h.age_component(comment.modified_at, time_is_local=True)}
106 </div>
110 </div>
107
111
108 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
112 % if comment.pull_request and comment.pull_request.author.user_id == comment.author.user_id:
109 <span class="tag authortag tooltip" title="${_('Pull request author')}">
113 <span class="tag authortag tooltip" title="${_('Pull request author')}">
110 ${_('author')}
114 ${_('author')}
111 </span>
115 </span>
112 % endif
116 % endif
113
117
114 <%
118 <%
115 comment_version_selector = 'comment_versions_{}'.format(comment.comment_id)
119 comment_version_selector = 'comment_versions_{}'.format(comment.comment_id)
116 %>
120 %>
117
121
118 % if comment.history:
122 % if comment.history:
119 <div class="date">
123 <div class="date">
120
124
121 <input id="${comment_version_selector}" name="${comment_version_selector}"
125 <input id="${comment_version_selector}" name="${comment_version_selector}"
122 type="hidden"
126 type="hidden"
123 data-last-version="${comment.history[-1].version}">
127 data-last-version="${comment.history[-1].version}">
124
128
125 <script type="text/javascript">
129 <script type="text/javascript">
126
130
127 var preLoadVersionData = [
131 var preLoadVersionData = [
128 % for comment_history in comment.history:
132 % for comment_history in comment.history:
129 {
133 {
130 id: ${comment_history.comment_history_id},
134 id: ${comment_history.comment_history_id},
131 text: 'v${comment_history.version}',
135 text: 'v${comment_history.version}',
132 action: function () {
136 action: function () {
133 Rhodecode.comments.showVersion(
137 Rhodecode.comments.showVersion(
134 "${comment.comment_id}",
138 "${comment.comment_id}",
135 "${comment_history.comment_history_id}"
139 "${comment_history.comment_history_id}"
136 )
140 )
137 },
141 },
138 comment_version: "${comment_history.version}",
142 comment_version: "${comment_history.version}",
139 comment_author_username: "${comment_history.author.username}",
143 comment_author_username: "${comment_history.author.username}",
140 comment_author_gravatar: "${h.gravatar_url(comment_history.author.email, 16)}",
144 comment_author_gravatar: "${h.gravatar_url(comment_history.author.email, 16)}",
141 comment_created_on: '${h.age_component(comment_history.created_on, time_is_local=True)}',
145 comment_created_on: '${h.age_component(comment_history.created_on, time_is_local=True)}',
142 },
146 },
143 % endfor
147 % endfor
144 ]
148 ]
145 initVersionSelector("#${comment_version_selector}", {results: preLoadVersionData});
149 initVersionSelector("#${comment_version_selector}", {results: preLoadVersionData});
146
150
147 </script>
151 </script>
148
152
149 </div>
153 </div>
150 % else:
154 % else:
151 <div class="date" style="display: none">
155 <div class="date" style="display: none">
152 <input id="${comment_version_selector}" name="${comment_version_selector}"
156 <input id="${comment_version_selector}" name="${comment_version_selector}"
153 type="hidden"
157 type="hidden"
154 data-last-version="0">
158 data-last-version="0">
155 </div>
159 </div>
156 %endif
160 %endif
157
161
158 <a class="permalink" href="#comment-${comment.comment_id}">&para; #${comment.comment_id}</a>
159
160 <div class="comment-links-block">
162 <div class="comment-links-block">
161
163
162 % if inline:
164 % if inline:
163 <a class="pr-version-inline" href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
165 <a class="pr-version-inline" href="${request.current_route_path(_query=dict(version=comment.pull_request_version_id), _anchor='comment-{}'.format(comment.comment_id))}">
164 % if outdated_at_ver:
166 % if outdated_at_ver:
165 <code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">
167 <code class="tooltip pr-version-num" title="${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">outdated ${'v{}'.format(comment_ver)}</code>
166 outdated ${'v{}'.format(comment_ver)} |
168 <code class="action-divider">|</code>
167 </code>
168 % elif comment_ver:
169 % elif comment_ver:
169 <code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">
170 <code class="tooltip pr-version-num" title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}">${'v{}'.format(comment_ver)}</code>
170 ${'v{}'.format(comment_ver)} |
171 <code class="action-divider">|</code>
171 </code>
172 % endif
172 % endif
173 </a>
173 </a>
174 % else:
174 % else:
175 % if comment_ver:
175 % if comment_ver:
176
176
177 % if comment.outdated:
177 % if comment.outdated:
178 <a class="pr-version"
178 <a class="pr-version"
179 href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}"
179 href="?version=${comment.pull_request_version_id}#comment-${comment.comment_id}"
180 >
180 >
181 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}
181 ${_('Outdated comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}
182 </a> |
182 </a>
183 <code class="action-divider">|</code>
183 % else:
184 % else:
184 <a class="tooltip pr-version"
185 <a class="tooltip pr-version"
185 title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}"
186 title="${_('Comment from pull request version v{0}, latest v{1}').format(comment_ver, latest_ver)}"
186 href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}"
187 href="${h.route_path('pullrequest_show',repo_name=comment.pull_request.target_repo.repo_name,pull_request_id=comment.pull_request.pull_request_id, version=comment.pull_request_version_id)}"
187 >
188 >
188 <code class="pr-version-num">
189 <code class="pr-version-num">${'v{}'.format(comment_ver)}</code>
189 ${'v{}'.format(comment_ver)}
190 </a>
190 </code>
191 <code class="action-divider">|</code>
191 </a> |
192 % endif
192 % endif
193
193
194 % endif
194 % endif
195 % endif
195 % endif
196
196
197 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
197 <details class="details-reset details-inline-block">
198 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
198 <summary class="noselect"><i class="icon-options cursor-pointer"></i></summary>
199 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
199 <details-menu class="details-dropdown">
200 ## permissions to delete
200
201 %if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id):
201 <div class="dropdown-item">
202 <a onclick="return Rhodecode.comments.editComment(this);"
202 ${_('Comment')} #${comment.comment_id}
203 class="edit-comment">${_('Edit')}</a>
203 <span class="pull-right icon-clipboard clipboard-action" data-clipboard-text="${comment_model.get_url(comment,request, permalink=True, anchor='comment-{}'.format(comment.comment_id))}" title="${_('Copy permalink')}"></span>
204 | <a onclick="return Rhodecode.comments.deleteComment(this);"
204 </div>
205 class="delete-comment">${_('Delete')}</a>
206 %else:
207 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
208 | <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
209 %endif
210 %else:
211 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
212 | <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
213 %endif
214
205
206 ## show delete comment if it's not a PR (regular comments) or it's PR that is not closed
207 ## only super-admin, repo admin OR comment owner can delete, also hide delete if currently viewed comment is outdated
208 %if not outdated_at_ver and (not comment.pull_request or (comment.pull_request and not comment.pull_request.is_closed())):
209 ## permissions to delete
210 %if comment.immutable is False and (c.is_super_admin or h.HasRepoPermissionAny('repository.admin')(c.repo_name) or comment.author.user_id == c.rhodecode_user.user_id):
211 <div class="dropdown-divider"></div>
212 <div class="dropdown-item">
213 <a onclick="return Rhodecode.comments.editComment(this);" class="btn btn-link btn-sm edit-comment">${_('Edit')}</a>
214 </div>
215 <div class="dropdown-item">
216 <a onclick="return Rhodecode.comments.deleteComment(this);" class="btn btn-link btn-sm btn-danger delete-comment">${_('Delete')}</a>
217 </div>
218 %else:
219 <div class="dropdown-divider"></div>
220 <div class="dropdown-item">
221 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
222 </div>
223 <div class="dropdown-item">
224 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
225 </div>
226 %endif
227 %else:
228 <div class="dropdown-divider"></div>
229 <div class="dropdown-item">
230 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Edit')}</a>
231 </div>
232 <div class="dropdown-item">
233 <a class="tooltip edit-comment link-disabled" disabled="disabled" title="${_('Action unavailable')}">${_('Delete')}</a>
234 </div>
235 %endif
236 </details-menu>
237 </details>
238
239 <code class="action-divider">|</code>
215 % if outdated_at_ver:
240 % if outdated_at_ver:
216 | <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous outdated comment')}"> <i class="icon-angle-left"></i> </a>
241 <a onclick="return Rhodecode.comments.prevOutdatedComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous outdated comment')}"> <i class="icon-angle-left"></i> </a>
217 | <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="tooltip next-comment" title="${_('Jump to the next outdated comment')}"> <i class="icon-angle-right"></i></a>
242 <a onclick="return Rhodecode.comments.nextOutdatedComment(this);" class="tooltip next-comment" title="${_('Jump to the next outdated comment')}"> <i class="icon-angle-right"></i></a>
218 % else:
243 % else:
219 | <a onclick="return Rhodecode.comments.prevComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous comment')}"> <i class="icon-angle-left"></i></a>
244 <a onclick="return Rhodecode.comments.prevComment(this);" class="tooltip prev-comment" title="${_('Jump to the previous comment')}"> <i class="icon-angle-left"></i></a>
220 | <a onclick="return Rhodecode.comments.nextComment(this);" class="tooltip next-comment" title="${_('Jump to the next comment')}"> <i class="icon-angle-right"></i></a>
245 <a onclick="return Rhodecode.comments.nextComment(this);" class="tooltip next-comment" title="${_('Jump to the next comment')}"> <i class="icon-angle-right"></i></a>
221 % endif
246 % endif
222
247
223 </div>
248 </div>
224 </div>
249 </div>
225 <div class="text">
250 <div class="text">
226 ${h.render(comment.text, renderer=comment.renderer, mentions=True, repo_name=getattr(c, 'repo_name', None), active_pattern_entries=active_pattern_entries)}
251 ${h.render(comment.text, renderer=comment.renderer, mentions=True, repo_name=getattr(c, 'repo_name', None), active_pattern_entries=active_pattern_entries)}
227 </div>
252 </div>
228
253
229 </div>
254 </div>
230 </%def>
255 </%def>
231
256
232 ## generate main comments
257 ## generate main comments
233 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
258 <%def name="generate_comments(comments, include_pull_request=False, is_pull_request=False)">
234 <%
259 <%
235 active_pattern_entries = h.get_active_pattern_entries(getattr(c, 'repo_name', None))
260 active_pattern_entries = h.get_active_pattern_entries(getattr(c, 'repo_name', None))
236 %>
261 %>
237
262
238 <div class="general-comments" id="comments">
263 <div class="general-comments" id="comments">
239 %for comment in comments:
264 %for comment in comments:
240 <div id="comment-tr-${comment.comment_id}">
265 <div id="comment-tr-${comment.comment_id}">
241 ## only render comments that are not from pull request, or from
266 ## only render comments that are not from pull request, or from
242 ## pull request and a status change
267 ## pull request and a status change
243 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
268 %if not comment.pull_request or (comment.pull_request and comment.status_change) or include_pull_request:
244 ${comment_block(comment, active_pattern_entries=active_pattern_entries)}
269 ${comment_block(comment, active_pattern_entries=active_pattern_entries)}
245 %endif
270 %endif
246 </div>
271 </div>
247 %endfor
272 %endfor
248 ## to anchor ajax comments
273 ## to anchor ajax comments
249 <div id="injected_page_comments"></div>
274 <div id="injected_page_comments"></div>
250 </div>
275 </div>
251 </%def>
276 </%def>
252
277
253
278
254 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
279 <%def name="comments(post_url, cur_status, is_pull_request=False, is_compare=False, change_status=True, form_extras=None)">
255
280
256 <div class="comments">
281 <div class="comments">
257 <%
282 <%
258 if is_pull_request:
283 if is_pull_request:
259 placeholder = _('Leave a comment on this Pull Request.')
284 placeholder = _('Leave a comment on this Pull Request.')
260 elif is_compare:
285 elif is_compare:
261 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
286 placeholder = _('Leave a comment on {} commits in this range.').format(len(form_extras))
262 else:
287 else:
263 placeholder = _('Leave a comment on this Commit.')
288 placeholder = _('Leave a comment on this Commit.')
264 %>
289 %>
265
290
266 % if c.rhodecode_user.username != h.DEFAULT_USER:
291 % if c.rhodecode_user.username != h.DEFAULT_USER:
267 <div class="js-template" id="cb-comment-general-form-template">
292 <div class="js-template" id="cb-comment-general-form-template">
268 ## template generated for injection
293 ## template generated for injection
269 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
294 ${comment_form(form_type='general', review_statuses=c.commit_statuses, form_extras=form_extras)}
270 </div>
295 </div>
271
296
272 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
297 <div id="cb-comment-general-form-placeholder" class="comment-form ac">
273 ## inject form here
298 ## inject form here
274 </div>
299 </div>
275 <script type="text/javascript">
300 <script type="text/javascript">
276 var lineNo = 'general';
301 var lineNo = 'general';
277 var resolvesCommentId = null;
302 var resolvesCommentId = null;
278 var generalCommentForm = Rhodecode.comments.createGeneralComment(
303 var generalCommentForm = Rhodecode.comments.createGeneralComment(
279 lineNo, "${placeholder}", resolvesCommentId);
304 lineNo, "${placeholder}", resolvesCommentId);
280
305
281 // set custom success callback on rangeCommit
306 // set custom success callback on rangeCommit
282 % if is_compare:
307 % if is_compare:
283 generalCommentForm.setHandleFormSubmit(function(o) {
308 generalCommentForm.setHandleFormSubmit(function(o) {
284 var self = generalCommentForm;
309 var self = generalCommentForm;
285
310
286 var text = self.cm.getValue();
311 var text = self.cm.getValue();
287 var status = self.getCommentStatus();
312 var status = self.getCommentStatus();
288 var commentType = self.getCommentType();
313 var commentType = self.getCommentType();
289
314
290 if (text === "" && !status) {
315 if (text === "" && !status) {
291 return;
316 return;
292 }
317 }
293
318
294 // we can pick which commits we want to make the comment by
319 // we can pick which commits we want to make the comment by
295 // selecting them via click on preview pane, this will alter the hidden inputs
320 // selecting them via click on preview pane, this will alter the hidden inputs
296 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
321 var cherryPicked = $('#changeset_compare_view_content .compare_select.hl').length > 0;
297
322
298 var commitIds = [];
323 var commitIds = [];
299 $('#changeset_compare_view_content .compare_select').each(function(el) {
324 $('#changeset_compare_view_content .compare_select').each(function(el) {
300 var commitId = this.id.replace('row-', '');
325 var commitId = this.id.replace('row-', '');
301 if ($(this).hasClass('hl') || !cherryPicked) {
326 if ($(this).hasClass('hl') || !cherryPicked) {
302 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
327 $("input[data-commit-id='{0}']".format(commitId)).val(commitId);
303 commitIds.push(commitId);
328 commitIds.push(commitId);
304 } else {
329 } else {
305 $("input[data-commit-id='{0}']".format(commitId)).val('')
330 $("input[data-commit-id='{0}']".format(commitId)).val('')
306 }
331 }
307 });
332 });
308
333
309 self.setActionButtonsDisabled(true);
334 self.setActionButtonsDisabled(true);
310 self.cm.setOption("readOnly", true);
335 self.cm.setOption("readOnly", true);
311 var postData = {
336 var postData = {
312 'text': text,
337 'text': text,
313 'changeset_status': status,
338 'changeset_status': status,
314 'comment_type': commentType,
339 'comment_type': commentType,
315 'commit_ids': commitIds,
340 'commit_ids': commitIds,
316 'csrf_token': CSRF_TOKEN
341 'csrf_token': CSRF_TOKEN
317 };
342 };
318
343
319 var submitSuccessCallback = function(o) {
344 var submitSuccessCallback = function(o) {
320 location.reload(true);
345 location.reload(true);
321 };
346 };
322 var submitFailCallback = function(){
347 var submitFailCallback = function(){
323 self.resetCommentFormState(text)
348 self.resetCommentFormState(text)
324 };
349 };
325 self.submitAjaxPOST(
350 self.submitAjaxPOST(
326 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
351 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
327 });
352 });
328 % endif
353 % endif
329
354
330 </script>
355 </script>
331 % else:
356 % else:
332 ## form state when not logged in
357 ## form state when not logged in
333 <div class="comment-form ac">
358 <div class="comment-form ac">
334
359
335 <div class="comment-area">
360 <div class="comment-area">
336 <div class="comment-area-header">
361 <div class="comment-area-header">
337 <ul class="nav-links clearfix">
362 <ul class="nav-links clearfix">
338 <li class="active">
363 <li class="active">
339 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
364 <a class="disabled" href="#edit-btn" disabled="disabled" onclick="return false">${_('Write')}</a>
340 </li>
365 </li>
341 <li class="">
366 <li class="">
342 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
367 <a class="disabled" href="#preview-btn" disabled="disabled" onclick="return false">${_('Preview')}</a>
343 </li>
368 </li>
344 </ul>
369 </ul>
345 </div>
370 </div>
346
371
347 <div class="comment-area-write" style="display: block;">
372 <div class="comment-area-write" style="display: block;">
348 <div id="edit-container">
373 <div id="edit-container">
349 <div style="padding: 40px 0">
374 <div style="padding: 40px 0">
350 ${_('You need to be logged in to leave comments.')}
375 ${_('You need to be logged in to leave comments.')}
351 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
376 <a href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">${_('Login now')}</a>
352 </div>
377 </div>
353 </div>
378 </div>
354 <div id="preview-container" class="clearfix" style="display: none;">
379 <div id="preview-container" class="clearfix" style="display: none;">
355 <div id="preview-box" class="preview-box"></div>
380 <div id="preview-box" class="preview-box"></div>
356 </div>
381 </div>
357 </div>
382 </div>
358
383
359 <div class="comment-area-footer">
384 <div class="comment-area-footer">
360 <div class="toolbar">
385 <div class="toolbar">
361 <div class="toolbar-text">
386 <div class="toolbar-text">
362 </div>
387 </div>
363 </div>
388 </div>
364 </div>
389 </div>
365 </div>
390 </div>
366
391
367 <div class="comment-footer">
392 <div class="comment-footer">
368 </div>
393 </div>
369
394
370 </div>
395 </div>
371 % endif
396 % endif
372
397
373 <script type="text/javascript">
398 <script type="text/javascript">
374 bindToggleButtons();
399 bindToggleButtons();
375 </script>
400 </script>
376 </div>
401 </div>
377 </%def>
402 </%def>
378
403
379
404
380 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
405 <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)">
381
406
382 ## comment injected based on assumption that user is logged in
407 ## comment injected based on assumption that user is logged in
383 <form ${('id="{}"'.format(form_id) if form_id else '') |n} action="#" method="GET">
408 <form ${('id="{}"'.format(form_id) if form_id else '') |n} action="#" method="GET">
384
409
385 <div class="comment-area">
410 <div class="comment-area">
386 <div class="comment-area-header">
411 <div class="comment-area-header">
387 <div class="pull-left">
412 <div class="pull-left">
388 <ul class="nav-links clearfix">
413 <ul class="nav-links clearfix">
389 <li class="active">
414 <li class="active">
390 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
415 <a href="#edit-btn" tabindex="-1" id="edit-btn_${lineno_id}">${_('Write')}</a>
391 </li>
416 </li>
392 <li class="">
417 <li class="">
393 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
418 <a href="#preview-btn" tabindex="-1" id="preview-btn_${lineno_id}">${_('Preview')}</a>
394 </li>
419 </li>
395 </ul>
420 </ul>
396 </div>
421 </div>
397 <div class="pull-right">
422 <div class="pull-right">
398 <span class="comment-area-text">${_('Mark as')}:</span>
423 <span class="comment-area-text">${_('Mark as')}:</span>
399 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
424 <select class="comment-type" id="comment_type_${lineno_id}" name="comment_type">
400 % for val in c.visual.comment_types:
425 % for val in c.visual.comment_types:
401 <option value="${val}">${val.upper()}</option>
426 <option value="${val}">${val.upper()}</option>
402 % endfor
427 % endfor
403 </select>
428 </select>
404 </div>
429 </div>
405 </div>
430 </div>
406
431
407 <div class="comment-area-write" style="display: block;">
432 <div class="comment-area-write" style="display: block;">
408 <div id="edit-container_${lineno_id}">
433 <div id="edit-container_${lineno_id}">
409 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
434 <textarea id="text_${lineno_id}" name="text" class="comment-block-ta ac-input"></textarea>
410 </div>
435 </div>
411 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
436 <div id="preview-container_${lineno_id}" class="clearfix" style="display: none;">
412 <div id="preview-box_${lineno_id}" class="preview-box"></div>
437 <div id="preview-box_${lineno_id}" class="preview-box"></div>
413 </div>
438 </div>
414 </div>
439 </div>
415
440
416 <div class="comment-area-footer comment-attachment-uploader">
441 <div class="comment-area-footer comment-attachment-uploader">
417 <div class="toolbar">
442 <div class="toolbar">
418
443
419 <div class="comment-attachment-text">
444 <div class="comment-attachment-text">
420 <div class="dropzone-text">
445 <div class="dropzone-text">
421 ${_("Drag'n Drop files here or")} <span class="link pick-attachment">${_('Choose your files')}</span>.<br>
446 ${_("Drag'n Drop files here or")} <span class="link pick-attachment">${_('Choose your files')}</span>.<br>
422 </div>
447 </div>
423 <div class="dropzone-upload" style="display:none">
448 <div class="dropzone-upload" style="display:none">
424 <i class="icon-spin animate-spin"></i> ${_('uploading...')}
449 <i class="icon-spin animate-spin"></i> ${_('uploading...')}
425 </div>
450 </div>
426 </div>
451 </div>
427
452
428 ## comments dropzone template, empty on purpose
453 ## comments dropzone template, empty on purpose
429 <div style="display: none" class="comment-attachment-uploader-template">
454 <div style="display: none" class="comment-attachment-uploader-template">
430 <div class="dz-file-preview" style="margin: 0">
455 <div class="dz-file-preview" style="margin: 0">
431 <div class="dz-error-message"></div>
456 <div class="dz-error-message"></div>
432 </div>
457 </div>
433 </div>
458 </div>
434
459
435 </div>
460 </div>
436 </div>
461 </div>
437 </div>
462 </div>
438
463
439 <div class="comment-footer">
464 <div class="comment-footer">
440
465
441 ## inject extra inputs into the form
466 ## inject extra inputs into the form
442 % if form_extras and isinstance(form_extras, (list, tuple)):
467 % if form_extras and isinstance(form_extras, (list, tuple)):
443 <div id="comment_form_extras">
468 <div id="comment_form_extras">
444 % for form_ex_el in form_extras:
469 % for form_ex_el in form_extras:
445 ${form_ex_el|n}
470 ${form_ex_el|n}
446 % endfor
471 % endfor
447 </div>
472 </div>
448 % endif
473 % endif
449
474
450 <div class="action-buttons">
475 <div class="action-buttons">
451 % if form_type != 'inline':
476 % if form_type != 'inline':
452 <div class="action-buttons-extra"></div>
477 <div class="action-buttons-extra"></div>
453 % endif
478 % endif
454
479
455 <input class="btn btn-success comment-button-input" id="save_${lineno_id}" name="save" type="submit" value="${_('Comment')}">
480 <input class="btn btn-success comment-button-input" id="save_${lineno_id}" name="save" type="submit" value="${_('Comment')}">
456
481
457 ## inline for has a file, and line-number together with cancel hide button.
482 ## inline for has a file, and line-number together with cancel hide button.
458 % if form_type == 'inline':
483 % if form_type == 'inline':
459 <input type="hidden" name="f_path" value="{0}">
484 <input type="hidden" name="f_path" value="{0}">
460 <input type="hidden" name="line" value="${lineno_id}">
485 <input type="hidden" name="line" value="${lineno_id}">
461 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
486 <button type="button" class="cb-comment-cancel" onclick="return Rhodecode.comments.cancelComment(this);">
462 ${_('Cancel')}
487 ${_('Cancel')}
463 </button>
488 </button>
464 % endif
489 % endif
465 </div>
490 </div>
466
491
467 % if review_statuses:
492 % if review_statuses:
468 <div class="status_box">
493 <div class="status_box">
469 <select id="change_status_${lineno_id}" name="changeset_status">
494 <select id="change_status_${lineno_id}" name="changeset_status">
470 <option></option> ## Placeholder
495 <option></option> ## Placeholder
471 % for status, lbl in review_statuses:
496 % for status, lbl in review_statuses:
472 <option value="${status}" data-status="${status}">${lbl}</option>
497 <option value="${status}" data-status="${status}">${lbl}</option>
473 %if is_pull_request and change_status and status in ('approved', 'rejected'):
498 %if is_pull_request and change_status and status in ('approved', 'rejected'):
474 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
499 <option value="${status}_closed" data-status="${status}">${lbl} & ${_('Closed')}</option>
475 %endif
500 %endif
476 % endfor
501 % endfor
477 </select>
502 </select>
478 </div>
503 </div>
479 % endif
504 % endif
480
505
481 <div class="toolbar-text">
506 <div class="toolbar-text">
482 <% renderer_url = '<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper()) %>
507 <% renderer_url = '<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper()) %>
483 ${_('Comments parsed using {} syntax.').format(renderer_url)|n} <br/>
508 ${_('Comments parsed using {} syntax.').format(renderer_url)|n} <br/>
484 <span class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span>
509 <span class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span>
485 ${_('and')}
510 ${_('and')}
486 <span class="tooltip" title="${_('Start typing with / for certain actions to be triggered via text box.')}">`/` autocomplete</span>
511 <span class="tooltip" title="${_('Start typing with / for certain actions to be triggered via text box.')}">`/` autocomplete</span>
487 ${_('actions supported.')}
512 ${_('actions supported.')}
488 </div>
513 </div>
489 </div>
514 </div>
490
515
491 </form>
516 </form>
492
517
493 </%def> No newline at end of file
518 </%def>
@@ -1,116 +1,121 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('%s Commits') % c.repo_name} -
5 ${_('%s Commits') % c.repo_name} -
6 r${c.commit_ranges[0].idx}:${h.short_id(c.commit_ranges[0].raw_id)}
6 r${c.commit_ranges[0].idx}:${h.short_id(c.commit_ranges[0].raw_id)}
7 ...
7 ...
8 r${c.commit_ranges[-1].idx}:${h.short_id(c.commit_ranges[-1].raw_id)}
8 r${c.commit_ranges[-1].idx}:${h.short_id(c.commit_ranges[-1].raw_id)}
9 ${_ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
9 ${_ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
10 %if c.rhodecode_name:
10 %if c.rhodecode_name:
11 &middot; ${h.branding(c.rhodecode_name)}
11 &middot; ${h.branding(c.rhodecode_name)}
12 %endif
12 %endif
13 </%def>
13 </%def>
14
14
15 <%def name="breadcrumbs_links()"></%def>
15 <%def name="breadcrumbs_links()"></%def>
16
16
17 <%def name="menu_bar_nav()">
17 <%def name="menu_bar_nav()">
18 ${self.menu_items(active='repositories')}
18 ${self.menu_items(active='repositories')}
19 </%def>
19 </%def>
20
20
21 <%def name="menu_bar_subnav()">
21 <%def name="menu_bar_subnav()">
22 ${self.repo_menu(active='commits')}
22 ${self.repo_menu(active='commits')}
23 </%def>
23 </%def>
24
24
25 <%def name="main()">
25 <%def name="main()">
26
26
27 <div class="box">
27 <div class="box">
28 <div class="summary changeset">
28 <div class="summary changeset">
29 <div class="summary-detail">
29 <div class="summary-detail">
30 <div class="summary-detail-header">
30 <div class="summary-detail-header">
31 <span class="breadcrumbs files_location">
31 <span class="breadcrumbs files_location">
32 <h4>
32 <h4>
33 ${_('Commit Range')}
33 ${_('Commit Range')}
34 </h4>
34 </h4>
35 </span>
35 </span>
36
36
37 <div class="clear-fix"></div>
37 <div class="clear-fix"></div>
38 </div>
38 </div>
39
39
40 <div class="fieldset">
40 <div class="fieldset">
41 <div class="left-label-summary">
41 <div class="left-label-summary">
42 <p class="spacing">${_('Range')}:</p>
42 <p class="spacing">${_('Range')}:</p>
43 <div class="right-label-summary">
43 <div class="right-label-summary">
44 <div class="code-header" >
44 <div class="code-header" >
45 <div class="compare_header">
45 <div class="compare_header">
46 <code class="fieldset-text-line">
46 <code class="fieldset-text-line">
47 r${c.commit_ranges[0].idx}:${h.short_id(c.commit_ranges[0].raw_id)}
47 r${c.commit_ranges[0].idx}:${h.short_id(c.commit_ranges[0].raw_id)}
48 ...
48 ...
49 r${c.commit_ranges[-1].idx}:${h.short_id(c.commit_ranges[-1].raw_id)}
49 r${c.commit_ranges[-1].idx}:${h.short_id(c.commit_ranges[-1].raw_id)}
50 ${_ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
50 ${_ungettext('(%s commit)','(%s commits)', len(c.commit_ranges)) % len(c.commit_ranges)}
51 </code>
51 </code>
52 </div>
52 </div>
53 </div>
53 </div>
54 </div>
54 </div>
55 </div>
55 </div>
56 </div>
56 </div>
57
57
58 <div class="fieldset">
58 <div class="fieldset">
59 <div class="left-label-summary">
59 <div class="left-label-summary">
60 <p class="spacing">${_('Diff Option')}:</p>
60 <p class="spacing">${_('Diff Option')}:</p>
61 <div class="right-label-summary">
61 <div class="right-label-summary">
62 <div class="code-header" >
62 <div class="code-header" >
63 <div class="compare_header">
63 <div class="compare_header">
64 <a class="btn btn-primary" href="${h.route_path('repo_compare',
64 <a class="btn btn-primary" href="${h.route_path('repo_compare',
65 repo_name=c.repo_name,
65 repo_name=c.repo_name,
66 source_ref_type='rev',
66 source_ref_type='rev',
67 source_ref=getattr(c.commit_ranges[0].parents[0] if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id'),
67 source_ref=getattr(c.commit_ranges[0].parents[0] if c.commit_ranges[0].parents else h.EmptyCommit(), 'raw_id'),
68 target_ref_type='rev',
68 target_ref_type='rev',
69 target_ref=c.commit_ranges[-1].raw_id)}"
69 target_ref=c.commit_ranges[-1].raw_id)}"
70 >
70 >
71 ${_('Show combined diff')}
71 ${_('Show combined diff')}
72 </a>
72 </a>
73 </div>
73 </div>
74 </div>
74 </div>
75 </div>
75 </div>
76 </div>
76 </div>
77 </div>
77 </div>
78
78
79 <div class="clear-fix"></div>
79 <div class="clear-fix"></div>
80 </div> <!-- end summary-detail -->
80 </div> <!-- end summary-detail -->
81 </div> <!-- end summary -->
81 </div> <!-- end summary -->
82
82
83 <div id="changeset_compare_view_content">
83 <div id="changeset_compare_view_content">
84 <div class="pull-left">
84 <div class="pull-left">
85 <div class="btn-group">
85 <div class="btn-group">
86 <a class="${('collapsed' if c.collapse_all_commits else '')}" href="#expand-commits" onclick="toggleCommitExpand(this); return false" data-toggle-commits-cnt=${len(c.commit_ranges)} >
86 <a class="${('collapsed' if c.collapse_all_commits else '')}" href="#expand-commits" onclick="toggleCommitExpand(this); return false" data-toggle-commits-cnt=${len(c.commit_ranges)} >
87 % if c.collapse_all_commits:
87 % if c.collapse_all_commits:
88 <i class="icon-plus-squared-alt icon-no-margin"></i>
88 <i class="icon-plus-squared-alt icon-no-margin"></i>
89 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
89 ${_ungettext('Expand {} commit', 'Expand {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
90 % else:
90 % else:
91 <i class="icon-minus-squared-alt icon-no-margin"></i>
91 <i class="icon-minus-squared-alt icon-no-margin"></i>
92 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
92 ${_ungettext('Collapse {} commit', 'Collapse {} commits', len(c.commit_ranges)).format(len(c.commit_ranges))}
93 % endif
93 % endif
94 </a>
94 </a>
95 </div>
95 </div>
96 </div>
96 </div>
97 ## Commit range generated below
97 ## Commit range generated below
98 <%include file="../compare/compare_commits.mako"/>
98 <%include file="../compare/compare_commits.mako"/>
99 <div class="cs_files">
99 <div class="cs_files">
100 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
100 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
101 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
101 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
102 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
102 <%namespace name="diff_block" file="/changeset/diff_block.mako"/>
103
103
104 %for commit in c.commit_ranges:
104 %for commit in c.commit_ranges:
105 ## commit range header for each individual diff
106 <h3>
107 <a class="tooltip revision" title="${h.tooltip(commit.message)}" href="${h.route_path('repo_commit',repo_name=c.repo_name,commit_id=commit.raw_id)}">${('r%s:%s' % (commit.idx,h.short_id(commit.raw_id)))}</a>
108 </h3>
109
105 ${cbdiffs.render_diffset_menu(c.changes[commit.raw_id])}
110 ${cbdiffs.render_diffset_menu(c.changes[commit.raw_id])}
106 ${cbdiffs.render_diffset(
111 ${cbdiffs.render_diffset(
107 diffset=c.changes[commit.raw_id],
112 diffset=c.changes[commit.raw_id],
108 collapse_when_files_over=5,
113 collapse_when_files_over=5,
109 commit=commit,
114 commit=commit,
110 )}
115 )}
111 %endfor
116 %endfor
112 </div>
117 </div>
113 </div>
118 </div>
114 </div>
119 </div>
115
120
116 </%def>
121 </%def>
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now