##// END OF EJS Templates
pull-requests: allow markup rendered description.
marcink -
r2816:b1852ba4 default
parent child Browse files
Show More
@@ -1,49 +1,53 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20 from rhodecode.config import routing_links
21 21
22 22
23 23 def includeme(config):
24 24
25 25 config.add_route(
26 26 name='home',
27 27 pattern='/')
28 28
29 29 config.add_route(
30 30 name='user_autocomplete_data',
31 31 pattern='/_users')
32 32
33 33 config.add_route(
34 34 name='user_group_autocomplete_data',
35 35 pattern='/_user_groups')
36 36
37 37 config.add_route(
38 38 name='repo_list_data',
39 39 pattern='/_repos')
40 40
41 41 config.add_route(
42 42 name='goto_switcher_data',
43 43 pattern='/_goto_data')
44 44
45 config.add_route(
46 name='markup_preview',
47 pattern='/_markup_preview')
48
45 49 # register our static links via redirection mechanism
46 50 routing_links.connect_redirection_links(config)
47 51
48 52 # Scan module for configuration decorators.
49 53 config.scan('.views', ignore='.tests')
@@ -1,427 +1,446 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2018 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import re
22 22 import logging
23 23 import collections
24 24
25 25 from pyramid.view import view_config
26 26
27 27 from rhodecode.apps._base import BaseAppView
28 28 from rhodecode.lib import helpers as h
29 29 from rhodecode.lib.auth import (
30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator)
30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator,
31 CSRFRequired)
31 32 from rhodecode.lib.index import searcher_from_config
32 33 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
33 34 from rhodecode.lib.ext_json import json
34 35 from rhodecode.model.db import (
35 36 func, or_, in_filter_generator, Repository, RepoGroup, User, UserGroup)
36 37 from rhodecode.model.repo import RepoModel
37 38 from rhodecode.model.repo_group import RepoGroupModel
38 39 from rhodecode.model.scm import RepoGroupList, RepoList
39 40 from rhodecode.model.user import UserModel
40 41 from rhodecode.model.user_group import UserGroupModel
41 42
42 43 log = logging.getLogger(__name__)
43 44
44 45
45 46 class HomeView(BaseAppView):
46 47
47 48 def load_default_context(self):
48 49 c = self._get_local_tmpl_context()
49 50 c.user = c.auth_user.get_instance()
50 51
51 52 return c
52 53
53 54 @LoginRequired()
54 55 @view_config(
55 56 route_name='user_autocomplete_data', request_method='GET',
56 57 renderer='json_ext', xhr=True)
57 58 def user_autocomplete_data(self):
58 59 self.load_default_context()
59 60 query = self.request.GET.get('query')
60 61 active = str2bool(self.request.GET.get('active') or True)
61 62 include_groups = str2bool(self.request.GET.get('user_groups'))
62 63 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
63 64 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
64 65
65 66 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
66 67 query, active, include_groups)
67 68
68 69 _users = UserModel().get_users(
69 70 name_contains=query, only_active=active)
70 71
71 72 def maybe_skip_default_user(usr):
72 73 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
73 74 return False
74 75 return True
75 76 _users = filter(maybe_skip_default_user, _users)
76 77
77 78 if include_groups:
78 79 # extend with user groups
79 80 _user_groups = UserGroupModel().get_user_groups(
80 81 name_contains=query, only_active=active,
81 82 expand_groups=expand_groups)
82 83 _users = _users + _user_groups
83 84
84 85 return {'suggestions': _users}
85 86
86 87 @LoginRequired()
87 88 @NotAnonymous()
88 89 @view_config(
89 90 route_name='user_group_autocomplete_data', request_method='GET',
90 91 renderer='json_ext', xhr=True)
91 92 def user_group_autocomplete_data(self):
92 93 self.load_default_context()
93 94 query = self.request.GET.get('query')
94 95 active = str2bool(self.request.GET.get('active') or True)
95 96 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
96 97
97 98 log.debug('generating user group list, query:%s, active:%s',
98 99 query, active)
99 100
100 101 _user_groups = UserGroupModel().get_user_groups(
101 102 name_contains=query, only_active=active,
102 103 expand_groups=expand_groups)
103 104 _user_groups = _user_groups
104 105
105 106 return {'suggestions': _user_groups}
106 107
107 108 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
108 109 org_query = name_contains
109 110 allowed_ids = self._rhodecode_user.repo_acl_ids(
110 111 ['repository.read', 'repository.write', 'repository.admin'],
111 112 cache=False, name_filter=name_contains) or [-1]
112 113
113 114 query = Repository.query()\
114 115 .order_by(func.length(Repository.repo_name))\
115 116 .order_by(Repository.repo_name)\
116 117 .filter(or_(
117 118 # generate multiple IN to fix limitation problems
118 119 *in_filter_generator(Repository.repo_id, allowed_ids)
119 120 ))
120 121
121 122 if repo_type:
122 123 query = query.filter(Repository.repo_type == repo_type)
123 124
124 125 if name_contains:
125 126 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
126 127 query = query.filter(
127 128 Repository.repo_name.ilike(ilike_expression))
128 129 query = query.limit(limit)
129 130
130 131 acl_iter = query
131 132
132 133 return [
133 134 {
134 135 'id': obj.repo_name,
135 136 'value': org_query,
136 137 'value_display': obj.repo_name,
137 138 'text': obj.repo_name,
138 139 'type': 'repo',
139 140 'repo_id': obj.repo_id,
140 141 'repo_type': obj.repo_type,
141 142 'private': obj.private,
142 143 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
143 144 }
144 145 for obj in acl_iter]
145 146
146 147 def _get_repo_group_list(self, name_contains=None, limit=20):
147 148 org_query = name_contains
148 149 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
149 150 ['group.read', 'group.write', 'group.admin'],
150 151 cache=False, name_filter=name_contains) or [-1]
151 152
152 153 query = RepoGroup.query()\
153 154 .order_by(func.length(RepoGroup.group_name))\
154 155 .order_by(RepoGroup.group_name) \
155 156 .filter(or_(
156 157 # generate multiple IN to fix limitation problems
157 158 *in_filter_generator(RepoGroup.group_id, allowed_ids)
158 159 ))
159 160
160 161 if name_contains:
161 162 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
162 163 query = query.filter(
163 164 RepoGroup.group_name.ilike(ilike_expression))
164 165 query = query.limit(limit)
165 166
166 167 acl_iter = query
167 168
168 169 return [
169 170 {
170 171 'id': obj.group_name,
171 172 'value': org_query,
172 173 'value_display': obj.group_name,
173 174 'type': 'repo_group',
174 175 'url': h.route_path(
175 176 'repo_group_home', repo_group_name=obj.group_name)
176 177 }
177 178 for obj in acl_iter]
178 179
179 180 def _get_user_list(self, name_contains=None, limit=20):
180 181 org_query = name_contains
181 182 if not name_contains:
182 183 return []
183 184
184 185 name_contains = re.compile('(?:user:)(.+)').findall(name_contains)
185 186 if len(name_contains) != 1:
186 187 return []
187 188 name_contains = name_contains[0]
188 189
189 190 query = User.query()\
190 191 .order_by(func.length(User.username))\
191 192 .order_by(User.username) \
192 193 .filter(User.username != User.DEFAULT_USER)
193 194
194 195 if name_contains:
195 196 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
196 197 query = query.filter(
197 198 User.username.ilike(ilike_expression))
198 199 query = query.limit(limit)
199 200
200 201 acl_iter = query
201 202
202 203 return [
203 204 {
204 205 'id': obj.user_id,
205 206 'value': org_query,
206 207 'value_display': obj.username,
207 208 'type': 'user',
208 209 'icon_link': h.gravatar_url(obj.email, 30),
209 210 'url': h.route_path(
210 211 'user_profile', username=obj.username)
211 212 }
212 213 for obj in acl_iter]
213 214
214 215 def _get_user_groups_list(self, name_contains=None, limit=20):
215 216 org_query = name_contains
216 217 if not name_contains:
217 218 return []
218 219
219 220 name_contains = re.compile('(?:user_group:)(.+)').findall(name_contains)
220 221 if len(name_contains) != 1:
221 222 return []
222 223 name_contains = name_contains[0]
223 224
224 225 query = UserGroup.query()\
225 226 .order_by(func.length(UserGroup.users_group_name))\
226 227 .order_by(UserGroup.users_group_name)
227 228
228 229 if name_contains:
229 230 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
230 231 query = query.filter(
231 232 UserGroup.users_group_name.ilike(ilike_expression))
232 233 query = query.limit(limit)
233 234
234 235 acl_iter = query
235 236
236 237 return [
237 238 {
238 239 'id': obj.users_group_id,
239 240 'value': org_query,
240 241 'value_display': obj.users_group_name,
241 242 'type': 'user_group',
242 243 'url': h.route_path(
243 244 'user_group_profile', user_group_name=obj.users_group_name)
244 245 }
245 246 for obj in acl_iter]
246 247
247 248 def _get_hash_commit_list(self, auth_user, query):
248 249 org_query = query
249 250 if not query or len(query) < 3:
250 251 return []
251 252
252 253 commit_hashes = re.compile('(?:commit:)([0-9a-f]{2,40})').findall(query)
253 254
254 255 if len(commit_hashes) != 1:
255 256 return []
256 257 commit_hash = commit_hashes[0]
257 258
258 259 searcher = searcher_from_config(self.request.registry.settings)
259 260 result = searcher.search(
260 261 'commit_id:%s*' % commit_hash, 'commit', auth_user,
261 262 raise_on_exc=False)
262 263
263 264 return [
264 265 {
265 266 'id': entry['commit_id'],
266 267 'value': org_query,
267 268 'value_display': 'repo `{}` commit: {}'.format(
268 269 entry['repository'], entry['commit_id']),
269 270 'type': 'commit',
270 271 'repo': entry['repository'],
271 272 'url': h.route_path(
272 273 'repo_commit',
273 274 repo_name=entry['repository'], commit_id=entry['commit_id'])
274 275 }
275 276 for entry in result['results']]
276 277
277 278 @LoginRequired()
278 279 @view_config(
279 280 route_name='repo_list_data', request_method='GET',
280 281 renderer='json_ext', xhr=True)
281 282 def repo_list_data(self):
282 283 _ = self.request.translate
283 284 self.load_default_context()
284 285
285 286 query = self.request.GET.get('query')
286 287 repo_type = self.request.GET.get('repo_type')
287 288 log.debug('generating repo list, query:%s, repo_type:%s',
288 289 query, repo_type)
289 290
290 291 res = []
291 292 repos = self._get_repo_list(query, repo_type=repo_type)
292 293 if repos:
293 294 res.append({
294 295 'text': _('Repositories'),
295 296 'children': repos
296 297 })
297 298
298 299 data = {
299 300 'more': False,
300 301 'results': res
301 302 }
302 303 return data
303 304
304 305 @LoginRequired()
305 306 @view_config(
306 307 route_name='goto_switcher_data', request_method='GET',
307 308 renderer='json_ext', xhr=True)
308 309 def goto_switcher_data(self):
309 310 c = self.load_default_context()
310 311
311 312 _ = self.request.translate
312 313
313 314 query = self.request.GET.get('query')
314 315 log.debug('generating main filter data, query %s', query)
315 316
316 317 default_search_val = u'Full text search for: `{}`'.format(query)
317 318 res = []
318 319 if not query:
319 320 return {'suggestions': res}
320 321
321 322 res.append({
322 323 'id': -1,
323 324 'value': query,
324 325 'value_display': default_search_val,
325 326 'type': 'search',
326 327 'url': h.route_path(
327 328 'search', _query={'q': query})
328 329 })
329 330 repo_group_id = safe_int(self.request.GET.get('repo_group_id'))
330 331 if repo_group_id:
331 332 repo_group = RepoGroup.get(repo_group_id)
332 333 composed_hint = '{}/{}'.format(repo_group.group_name, query)
333 334 show_hint = not query.startswith(repo_group.group_name)
334 335 if repo_group and show_hint:
335 336 hint = u'Group search: `{}`'.format(composed_hint)
336 337 res.append({
337 338 'id': -1,
338 339 'value': composed_hint,
339 340 'value_display': hint,
340 341 'type': 'hint',
341 342 'url': ""
342 343 })
343 344
344 345 repo_groups = self._get_repo_group_list(query)
345 346 for serialized_repo_group in repo_groups:
346 347 res.append(serialized_repo_group)
347 348
348 349 repos = self._get_repo_list(query)
349 350 for serialized_repo in repos:
350 351 res.append(serialized_repo)
351 352
352 353 # TODO(marcink): permissions for that ?
353 354 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
354 355 if allowed_user_search:
355 356 users = self._get_user_list(query)
356 357 for serialized_user in users:
357 358 res.append(serialized_user)
358 359
359 360 user_groups = self._get_user_groups_list(query)
360 361 for serialized_user_group in user_groups:
361 362 res.append(serialized_user_group)
362 363
363 364 commits = self._get_hash_commit_list(c.auth_user, query)
364 365 if commits:
365 366 unique_repos = collections.OrderedDict()
366 367 for commit in commits:
367 368 repo_name = commit['repo']
368 369 unique_repos.setdefault(repo_name, []).append(commit)
369 370
370 371 for repo, commits in unique_repos.items():
371 372 for commit in commits:
372 373 res.append(commit)
373 374
374 375 return {'suggestions': res}
375 376
376 377 def _get_groups_and_repos(self, repo_group_id=None):
377 378 # repo groups groups
378 379 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
379 380 _perms = ['group.read', 'group.write', 'group.admin']
380 381 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
381 382 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
382 383 repo_group_list=repo_group_list_acl, admin=False)
383 384
384 385 # repositories
385 386 repo_list = Repository.get_all_repos(group_id=repo_group_id)
386 387 _perms = ['repository.read', 'repository.write', 'repository.admin']
387 388 repo_list_acl = RepoList(repo_list, perm_set=_perms)
388 389 repo_data = RepoModel().get_repos_as_dict(
389 390 repo_list=repo_list_acl, admin=False)
390 391
391 392 return repo_data, repo_group_data
392 393
393 394 @LoginRequired()
394 395 @view_config(
395 396 route_name='home', request_method='GET',
396 397 renderer='rhodecode:templates/index.mako')
397 398 def main_page(self):
398 399 c = self.load_default_context()
399 400 c.repo_group = None
400 401
401 402 repo_data, repo_group_data = self._get_groups_and_repos()
402 403 # json used to render the grids
403 404 c.repos_data = json.dumps(repo_data)
404 405 c.repo_groups_data = json.dumps(repo_group_data)
405 406
406 407 return self._get_template_context(c)
407 408
408 409 @LoginRequired()
409 410 @HasRepoGroupPermissionAnyDecorator(
410 411 'group.read', 'group.write', 'group.admin')
411 412 @view_config(
412 413 route_name='repo_group_home', request_method='GET',
413 414 renderer='rhodecode:templates/index_repo_group.mako')
414 415 @view_config(
415 416 route_name='repo_group_home_slash', request_method='GET',
416 417 renderer='rhodecode:templates/index_repo_group.mako')
417 418 def repo_group_main_page(self):
418 419 c = self.load_default_context()
419 420 c.repo_group = self.request.db_repo_group
420 421 repo_data, repo_group_data = self._get_groups_and_repos(
421 422 c.repo_group.group_id)
422 423
423 424 # json used to render the grids
424 425 c.repos_data = json.dumps(repo_data)
425 426 c.repo_groups_data = json.dumps(repo_group_data)
426 427
427 428 return self._get_template_context(c)
429
430 @LoginRequired()
431 @CSRFRequired()
432 @view_config(
433 route_name='markup_preview', request_method='POST',
434 renderer='string', xhr=True)
435 def markup_preview(self):
436 # Technically a CSRF token is not needed as no state changes with this
437 # call. However, as this is a POST is better to have it, so automated
438 # tools don't flag it as potential CSRF.
439 # Post is required because the payload could be bigger than the maximum
440 # allowed by GET.
441
442 text = self.request.POST.get('text')
443 renderer = self.request.POST.get('renderer') or 'rst'
444 if text:
445 return h.render(text, renderer=renderer, mentions=True)
446 return ''
@@ -1,2407 +1,2466 b''
1 1 //Primary CSS
2 2
3 3 //--- IMPORTS ------------------//
4 4
5 5 @import 'helpers';
6 6 @import 'mixins';
7 7 @import 'rcicons';
8 8 @import 'fonts';
9 9 @import 'variables';
10 10 @import 'bootstrap-variables';
11 11 @import 'form-bootstrap';
12 12 @import 'codemirror';
13 13 @import 'legacy_code_styles';
14 14 @import 'readme-box';
15 15 @import 'progress-bar';
16 16
17 17 @import 'type';
18 18 @import 'alerts';
19 19 @import 'buttons';
20 20 @import 'tags';
21 21 @import 'code-block';
22 22 @import 'examples';
23 23 @import 'login';
24 24 @import 'main-content';
25 25 @import 'select2';
26 26 @import 'comments';
27 27 @import 'panels-bootstrap';
28 28 @import 'panels';
29 29 @import 'deform';
30 30
31 31 //--- BASE ------------------//
32 32 .noscript-error {
33 33 top: 0;
34 34 left: 0;
35 35 width: 100%;
36 36 z-index: 101;
37 37 text-align: center;
38 38 font-family: @text-semibold;
39 39 font-size: 120%;
40 40 color: white;
41 41 background-color: @alert2;
42 42 padding: 5px 0 5px 0;
43 43 }
44 44
45 45 html {
46 46 display: table;
47 47 height: 100%;
48 48 width: 100%;
49 49 }
50 50
51 51 body {
52 52 display: table-cell;
53 53 width: 100%;
54 54 }
55 55
56 56 //--- LAYOUT ------------------//
57 57
58 58 .hidden{
59 59 display: none !important;
60 60 }
61 61
62 62 .box{
63 63 float: left;
64 64 width: 100%;
65 65 }
66 66
67 67 .browser-header {
68 68 clear: both;
69 69 }
70 70 .main {
71 71 clear: both;
72 72 padding:0 0 @pagepadding;
73 73 height: auto;
74 74
75 75 &:after { //clearfix
76 76 content:"";
77 77 clear:both;
78 78 width:100%;
79 79 display:block;
80 80 }
81 81 }
82 82
83 83 .action-link{
84 84 margin-left: @padding;
85 85 padding-left: @padding;
86 86 border-left: @border-thickness solid @border-default-color;
87 87 }
88 88
89 89 input + .action-link, .action-link.first{
90 90 border-left: none;
91 91 }
92 92
93 93 .action-link.last{
94 94 margin-right: @padding;
95 95 padding-right: @padding;
96 96 }
97 97
98 98 .action-link.active,
99 99 .action-link.active a{
100 100 color: @grey4;
101 101 }
102 102
103 103 .action-link.disabled {
104 104 color: @grey4;
105 105 cursor: inherit;
106 106 }
107 107
108 108 .clipboard-action {
109 109 cursor: pointer;
110 110 }
111 111
112 112 ul.simple-list{
113 113 list-style: none;
114 114 margin: 0;
115 115 padding: 0;
116 116 }
117 117
118 118 .main-content {
119 119 padding-bottom: @pagepadding;
120 120 }
121 121
122 122 .wide-mode-wrapper {
123 123 max-width:4000px !important;
124 124 }
125 125
126 126 .wrapper {
127 127 position: relative;
128 128 max-width: @wrapper-maxwidth;
129 129 margin: 0 auto;
130 130 }
131 131
132 132 #content {
133 133 clear: both;
134 134 padding: 0 @contentpadding;
135 135 }
136 136
137 137 .advanced-settings-fields{
138 138 input{
139 139 margin-left: @textmargin;
140 140 margin-right: @padding/2;
141 141 }
142 142 }
143 143
144 144 .cs_files_title {
145 145 margin: @pagepadding 0 0;
146 146 }
147 147
148 148 input.inline[type="file"] {
149 149 display: inline;
150 150 }
151 151
152 152 .error_page {
153 153 margin: 10% auto;
154 154
155 155 h1 {
156 156 color: @grey2;
157 157 }
158 158
159 159 .alert {
160 160 margin: @padding 0;
161 161 }
162 162
163 163 .error-branding {
164 164 font-family: @text-semibold;
165 165 color: @grey4;
166 166 }
167 167
168 168 .error_message {
169 169 font-family: @text-regular;
170 170 }
171 171
172 172 .sidebar {
173 173 min-height: 275px;
174 174 margin: 0;
175 175 padding: 0 0 @sidebarpadding @sidebarpadding;
176 176 border: none;
177 177 }
178 178
179 179 .main-content {
180 180 position: relative;
181 181 margin: 0 @sidebarpadding @sidebarpadding;
182 182 padding: 0 0 0 @sidebarpadding;
183 183 border-left: @border-thickness solid @grey5;
184 184
185 185 @media (max-width:767px) {
186 186 clear: both;
187 187 width: 100%;
188 188 margin: 0;
189 189 border: none;
190 190 }
191 191 }
192 192
193 193 .inner-column {
194 194 float: left;
195 195 width: 29.75%;
196 196 min-height: 150px;
197 197 margin: @sidebarpadding 2% 0 0;
198 198 padding: 0 2% 0 0;
199 199 border-right: @border-thickness solid @grey5;
200 200
201 201 @media (max-width:767px) {
202 202 clear: both;
203 203 width: 100%;
204 204 border: none;
205 205 }
206 206
207 207 ul {
208 208 padding-left: 1.25em;
209 209 }
210 210
211 211 &:last-child {
212 212 margin: @sidebarpadding 0 0;
213 213 border: none;
214 214 }
215 215
216 216 h4 {
217 217 margin: 0 0 @padding;
218 218 font-family: @text-semibold;
219 219 }
220 220 }
221 221 }
222 222 .error-page-logo {
223 223 width: 130px;
224 224 height: 160px;
225 225 }
226 226
227 227 // HEADER
228 228 .header {
229 229
230 230 // TODO: johbo: Fix login pages, so that they work without a min-height
231 231 // for the header and then remove the min-height. I chose a smaller value
232 232 // intentionally here to avoid rendering issues in the main navigation.
233 233 min-height: 49px;
234 234
235 235 position: relative;
236 236 vertical-align: bottom;
237 237 padding: 0 @header-padding;
238 238 background-color: @grey2;
239 239 color: @grey5;
240 240
241 241 .title {
242 242 overflow: visible;
243 243 }
244 244
245 245 &:before,
246 246 &:after {
247 247 content: "";
248 248 clear: both;
249 249 width: 100%;
250 250 }
251 251
252 252 // TODO: johbo: Avoids breaking "Repositories" chooser
253 253 .select2-container .select2-choice .select2-arrow {
254 254 display: none;
255 255 }
256 256 }
257 257
258 258 #header-inner {
259 259 &.title {
260 260 margin: 0;
261 261 }
262 262 &:before,
263 263 &:after {
264 264 content: "";
265 265 clear: both;
266 266 }
267 267 }
268 268
269 269 // Gists
270 270 #files_data {
271 271 clear: both; //for firefox
272 272 }
273 273 #gistid {
274 274 margin-right: @padding;
275 275 }
276 276
277 277 // Global Settings Editor
278 278 .textarea.editor {
279 279 float: left;
280 280 position: relative;
281 281 max-width: @texteditor-width;
282 282
283 283 select {
284 284 position: absolute;
285 285 top:10px;
286 286 right:0;
287 287 }
288 288
289 289 .CodeMirror {
290 290 margin: 0;
291 291 }
292 292
293 293 .help-block {
294 294 margin: 0 0 @padding;
295 295 padding:.5em;
296 296 background-color: @grey6;
297 297 &.pre-formatting {
298 298 white-space: pre;
299 299 }
300 300 }
301 301 }
302 302
303 303 ul.auth_plugins {
304 304 margin: @padding 0 @padding @legend-width;
305 305 padding: 0;
306 306
307 307 li {
308 308 margin-bottom: @padding;
309 309 line-height: 1em;
310 310 list-style-type: none;
311 311
312 312 .auth_buttons .btn {
313 313 margin-right: @padding;
314 314 }
315 315
316 316 &:before { content: none; }
317 317 }
318 318 }
319 319
320 320
321 321 // My Account PR list
322 322
323 323 #show_closed {
324 324 margin: 0 1em 0 0;
325 325 }
326 326
327 327 .pullrequestlist {
328 328 .closed {
329 329 background-color: @grey6;
330 330 }
331 331 .td-status {
332 332 padding-left: .5em;
333 333 }
334 334 .log-container .truncate {
335 335 height: 2.75em;
336 336 white-space: pre-line;
337 337 }
338 338 table.rctable .user {
339 339 padding-left: 0;
340 340 }
341 341 table.rctable {
342 342 td.td-description,
343 343 .rc-user {
344 344 min-width: auto;
345 345 }
346 346 }
347 347 }
348 348
349 349 // Pull Requests
350 350
351 351 .pullrequests_section_head {
352 352 display: block;
353 353 clear: both;
354 354 margin: @padding 0;
355 355 font-family: @text-bold;
356 356 }
357 357
358 358 .pr-origininfo, .pr-targetinfo {
359 359 position: relative;
360 360
361 361 .tag {
362 362 display: inline-block;
363 363 margin: 0 1em .5em 0;
364 364 }
365 365
366 366 .clone-url {
367 367 display: inline-block;
368 368 margin: 0 0 .5em 0;
369 369 padding: 0;
370 370 line-height: 1.2em;
371 371 }
372 372 }
373 373
374 374 .pr-mergeinfo {
375 375 min-width: 95% !important;
376 376 padding: 0 !important;
377 377 border: 0;
378 378 }
379 379 .pr-mergeinfo-copy {
380 380 padding: 0 0;
381 381 }
382 382
383 383 .pr-pullinfo {
384 384 min-width: 95% !important;
385 385 padding: 0 !important;
386 386 border: 0;
387 387 }
388 388 .pr-pullinfo-copy {
389 389 padding: 0 0;
390 390 }
391 391
392 392
393 393 #pr-title-input {
394 394 width: 72%;
395 395 font-size: 1em;
396 396 font-family: @text-bold;
397 397 margin: 0;
398 398 padding: 0 0 0 @padding/4;
399 399 line-height: 1.7em;
400 400 color: @text-color;
401 401 letter-spacing: .02em;
402 402 }
403 403
404 404 #pullrequest_title {
405 405 width: 100%;
406 406 box-sizing: border-box;
407 407 }
408 408
409 409 #pr_open_message {
410 410 border: @border-thickness solid #fff;
411 411 border-radius: @border-radius;
412 412 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
413 413 text-align: left;
414 414 overflow: hidden;
415 415 }
416 416
417 417 .pr-submit-button {
418 418 float: right;
419 419 margin: 0 0 0 5px;
420 420 }
421 421
422 422 .pr-spacing-container {
423 423 padding: 20px;
424 424 clear: both
425 425 }
426 426
427 427 #pr-description-input {
428 428 margin-bottom: 0;
429 429 }
430 430
431 431 .pr-description-label {
432 432 vertical-align: top;
433 433 }
434 434
435 435 .perms_section_head {
436 436 min-width: 625px;
437 437
438 438 h2 {
439 439 margin-bottom: 0;
440 440 }
441 441
442 442 .label-checkbox {
443 443 float: left;
444 444 }
445 445
446 446 &.field {
447 447 margin: @space 0 @padding;
448 448 }
449 449
450 450 &:first-child.field {
451 451 margin-top: 0;
452 452
453 453 .label {
454 454 margin-top: 0;
455 455 padding-top: 0;
456 456 }
457 457
458 458 .radios {
459 459 padding-top: 0;
460 460 }
461 461 }
462 462
463 463 .radios {
464 464 position: relative;
465 465 width: 405px;
466 466 }
467 467 }
468 468
469 469 //--- MODULES ------------------//
470 470
471 471
472 472 // Server Announcement
473 473 #server-announcement {
474 474 width: 95%;
475 475 margin: @padding auto;
476 476 padding: @padding;
477 477 border-width: 2px;
478 478 border-style: solid;
479 479 .border-radius(2px);
480 480 font-family: @text-bold;
481 481
482 482 &.info { border-color: @alert4; background-color: @alert4-inner; }
483 483 &.warning { border-color: @alert3; background-color: @alert3-inner; }
484 484 &.error { border-color: @alert2; background-color: @alert2-inner; }
485 485 &.success { border-color: @alert1; background-color: @alert1-inner; }
486 486 &.neutral { border-color: @grey3; background-color: @grey6; }
487 487 }
488 488
489 489 // Fixed Sidebar Column
490 490 .sidebar-col-wrapper {
491 491 padding-left: @sidebar-all-width;
492 492
493 493 .sidebar {
494 494 width: @sidebar-width;
495 495 margin-left: -@sidebar-all-width;
496 496 }
497 497 }
498 498
499 499 .sidebar-col-wrapper.scw-small {
500 500 padding-left: @sidebar-small-all-width;
501 501
502 502 .sidebar {
503 503 width: @sidebar-small-width;
504 504 margin-left: -@sidebar-small-all-width;
505 505 }
506 506 }
507 507
508 508
509 509 // FOOTER
510 510 #footer {
511 511 padding: 0;
512 512 text-align: center;
513 513 vertical-align: middle;
514 514 color: @grey2;
515 515 background-color: @grey6;
516 516
517 517 p {
518 518 margin: 0;
519 519 padding: 1em;
520 520 line-height: 1em;
521 521 }
522 522
523 523 .server-instance { //server instance
524 524 display: none;
525 525 }
526 526
527 527 .title {
528 528 float: none;
529 529 margin: 0 auto;
530 530 }
531 531 }
532 532
533 533 button.close {
534 534 padding: 0;
535 535 cursor: pointer;
536 536 background: transparent;
537 537 border: 0;
538 538 .box-shadow(none);
539 539 -webkit-appearance: none;
540 540 }
541 541
542 542 .close {
543 543 float: right;
544 544 font-size: 21px;
545 545 font-family: @text-bootstrap;
546 546 line-height: 1em;
547 547 font-weight: bold;
548 548 color: @grey2;
549 549
550 550 &:hover,
551 551 &:focus {
552 552 color: @grey1;
553 553 text-decoration: none;
554 554 cursor: pointer;
555 555 }
556 556 }
557 557
558 558 // GRID
559 559 .sorting,
560 560 .sorting_desc,
561 561 .sorting_asc {
562 562 cursor: pointer;
563 563 }
564 564 .sorting_desc:after {
565 565 content: "\00A0\25B2";
566 566 font-size: .75em;
567 567 }
568 568 .sorting_asc:after {
569 569 content: "\00A0\25BC";
570 570 font-size: .68em;
571 571 }
572 572
573 573
574 574 .user_auth_tokens {
575 575
576 576 &.truncate {
577 577 white-space: nowrap;
578 578 overflow: hidden;
579 579 text-overflow: ellipsis;
580 580 }
581 581
582 582 .fields .field .input {
583 583 margin: 0;
584 584 }
585 585
586 586 input#description {
587 587 width: 100px;
588 588 margin: 0;
589 589 }
590 590
591 591 .drop-menu {
592 592 // TODO: johbo: Remove this, should work out of the box when
593 593 // having multiple inputs inline
594 594 margin: 0 0 0 5px;
595 595 }
596 596 }
597 597 #user_list_table {
598 598 .closed {
599 599 background-color: @grey6;
600 600 }
601 601 }
602 602
603 603
604 604 input {
605 605 &.disabled {
606 606 opacity: .5;
607 607 }
608 608 }
609 609
610 610 // remove extra padding in firefox
611 611 input::-moz-focus-inner { border:0; padding:0 }
612 612
613 613 .adjacent input {
614 614 margin-bottom: @padding;
615 615 }
616 616
617 617 .permissions_boxes {
618 618 display: block;
619 619 }
620 620
621 621 //TODO: lisa: this should be in tables
622 622 .show_more_col {
623 623 width: 20px;
624 624 }
625 625
626 626 //FORMS
627 627
628 628 .medium-inline,
629 629 input#description.medium-inline {
630 630 display: inline;
631 631 width: @medium-inline-input-width;
632 632 min-width: 100px;
633 633 }
634 634
635 635 select {
636 636 //reset
637 637 -webkit-appearance: none;
638 638 -moz-appearance: none;
639 639
640 640 display: inline-block;
641 641 height: 28px;
642 642 width: auto;
643 643 margin: 0 @padding @padding 0;
644 644 padding: 0 18px 0 8px;
645 645 line-height:1em;
646 646 font-size: @basefontsize;
647 647 border: @border-thickness solid @rcblue;
648 648 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
649 649 color: @rcblue;
650 650
651 651 &:after {
652 652 content: "\00A0\25BE";
653 653 }
654 654
655 655 &:focus {
656 656 outline: none;
657 657 }
658 658 }
659 659
660 660 option {
661 661 &:focus {
662 662 outline: none;
663 663 }
664 664 }
665 665
666 666 input,
667 667 textarea {
668 668 padding: @input-padding;
669 669 border: @input-border-thickness solid @border-highlight-color;
670 670 .border-radius (@border-radius);
671 671 font-family: @text-light;
672 672 font-size: @basefontsize;
673 673
674 674 &.input-sm {
675 675 padding: 5px;
676 676 }
677 677
678 678 &#description {
679 679 min-width: @input-description-minwidth;
680 680 min-height: 1em;
681 681 padding: 10px;
682 682 }
683 683 }
684 684
685 685 .field-sm {
686 686 input,
687 687 textarea {
688 688 padding: 5px;
689 689 }
690 690 }
691 691
692 692 textarea {
693 693 display: block;
694 694 clear: both;
695 695 width: 100%;
696 696 min-height: 100px;
697 697 margin-bottom: @padding;
698 698 .box-sizing(border-box);
699 699 overflow: auto;
700 700 }
701 701
702 702 label {
703 703 font-family: @text-light;
704 704 }
705 705
706 706 // GRAVATARS
707 707 // centers gravatar on username to the right
708 708
709 709 .gravatar {
710 710 display: inline;
711 711 min-width: 16px;
712 712 min-height: 16px;
713 713 margin: -5px 0;
714 714 padding: 0;
715 715 line-height: 1em;
716 716 border: 1px solid @grey4;
717 717 box-sizing: content-box;
718 718
719 719 &.gravatar-large {
720 720 margin: -0.5em .25em -0.5em 0;
721 721 }
722 722
723 723 & + .user {
724 724 display: inline;
725 725 margin: 0;
726 726 padding: 0 0 0 .17em;
727 727 line-height: 1em;
728 728 }
729 729 }
730 730
731 731 .user-inline-data {
732 732 display: inline-block;
733 733 float: left;
734 734 padding-left: .5em;
735 735 line-height: 1.3em;
736 736 }
737 737
738 738 .rc-user { // gravatar + user wrapper
739 739 float: left;
740 740 position: relative;
741 741 min-width: 100px;
742 742 max-width: 200px;
743 743 min-height: (@gravatar-size + @border-thickness * 2); // account for border
744 744 display: block;
745 745 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
746 746
747 747
748 748 .gravatar {
749 749 display: block;
750 750 position: absolute;
751 751 top: 0;
752 752 left: 0;
753 753 min-width: @gravatar-size;
754 754 min-height: @gravatar-size;
755 755 margin: 0;
756 756 }
757 757
758 758 .user {
759 759 display: block;
760 760 max-width: 175px;
761 761 padding-top: 2px;
762 762 overflow: hidden;
763 763 text-overflow: ellipsis;
764 764 }
765 765 }
766 766
767 767 .gist-gravatar,
768 768 .journal_container {
769 769 .gravatar-large {
770 770 margin: 0 .5em -10px 0;
771 771 }
772 772 }
773 773
774 774
775 775 // ADMIN SETTINGS
776 776
777 777 // Tag Patterns
778 778 .tag_patterns {
779 779 .tag_input {
780 780 margin-bottom: @padding;
781 781 }
782 782 }
783 783
784 784 .locked_input {
785 785 position: relative;
786 786
787 787 input {
788 788 display: inline;
789 789 margin: 3px 5px 0px 0px;
790 790 }
791 791
792 792 br {
793 793 display: none;
794 794 }
795 795
796 796 .error-message {
797 797 float: left;
798 798 width: 100%;
799 799 }
800 800
801 801 .lock_input_button {
802 802 display: inline;
803 803 }
804 804
805 805 .help-block {
806 806 clear: both;
807 807 }
808 808 }
809 809
810 810 // Notifications
811 811
812 812 .notifications_buttons {
813 813 margin: 0 0 @space 0;
814 814 padding: 0;
815 815
816 816 .btn {
817 817 display: inline-block;
818 818 }
819 819 }
820 820
821 821 .notification-list {
822 822
823 823 div {
824 824 display: inline-block;
825 825 vertical-align: middle;
826 826 }
827 827
828 828 .container {
829 829 display: block;
830 830 margin: 0 0 @padding 0;
831 831 }
832 832
833 833 .delete-notifications {
834 834 margin-left: @padding;
835 835 text-align: right;
836 836 cursor: pointer;
837 837 }
838 838
839 839 .read-notifications {
840 840 margin-left: @padding/2;
841 841 text-align: right;
842 842 width: 35px;
843 843 cursor: pointer;
844 844 }
845 845
846 846 .icon-minus-sign {
847 847 color: @alert2;
848 848 }
849 849
850 850 .icon-ok-sign {
851 851 color: @alert1;
852 852 }
853 853 }
854 854
855 855 .user_settings {
856 856 float: left;
857 857 clear: both;
858 858 display: block;
859 859 width: 100%;
860 860
861 861 .gravatar_box {
862 862 margin-bottom: @padding;
863 863
864 864 &:after {
865 865 content: " ";
866 866 clear: both;
867 867 width: 100%;
868 868 }
869 869 }
870 870
871 871 .fields .field {
872 872 clear: both;
873 873 }
874 874 }
875 875
876 876 .advanced_settings {
877 877 margin-bottom: @space;
878 878
879 879 .help-block {
880 880 margin-left: 0;
881 881 }
882 882
883 883 button + .help-block {
884 884 margin-top: @padding;
885 885 }
886 886 }
887 887
888 888 // admin settings radio buttons and labels
889 889 .label-2 {
890 890 float: left;
891 891 width: @label2-width;
892 892
893 893 label {
894 894 color: @grey1;
895 895 }
896 896 }
897 897 .checkboxes {
898 898 float: left;
899 899 width: @checkboxes-width;
900 900 margin-bottom: @padding;
901 901
902 902 .checkbox {
903 903 width: 100%;
904 904
905 905 label {
906 906 margin: 0;
907 907 padding: 0;
908 908 }
909 909 }
910 910
911 911 .checkbox + .checkbox {
912 912 display: inline-block;
913 913 }
914 914
915 915 label {
916 916 margin-right: 1em;
917 917 }
918 918 }
919 919
920 920 // CHANGELOG
921 921 .container_header {
922 922 float: left;
923 923 display: block;
924 924 width: 100%;
925 925 margin: @padding 0 @padding;
926 926
927 927 #filter_changelog {
928 928 float: left;
929 929 margin-right: @padding;
930 930 }
931 931
932 932 .breadcrumbs_light {
933 933 display: inline-block;
934 934 }
935 935 }
936 936
937 937 .info_box {
938 938 float: right;
939 939 }
940 940
941 941
942 942 #graph_nodes {
943 943 padding-top: 43px;
944 944 }
945 945
946 946 #graph_content{
947 947
948 948 // adjust for table headers so that graph renders properly
949 949 // #graph_nodes padding - table cell padding
950 950 padding-top: (@space - (@basefontsize * 2.4));
951 951
952 952 &.graph_full_width {
953 953 width: 100%;
954 954 max-width: 100%;
955 955 }
956 956 }
957 957
958 958 #graph {
959 959 .flag_status {
960 960 margin: 0;
961 961 }
962 962
963 963 .pagination-left {
964 964 float: left;
965 965 clear: both;
966 966 }
967 967
968 968 .log-container {
969 969 max-width: 345px;
970 970
971 971 .message{
972 972 max-width: 340px;
973 973 }
974 974 }
975 975
976 976 .graph-col-wrapper {
977 977 padding-left: 110px;
978 978
979 979 #graph_nodes {
980 980 width: 100px;
981 981 margin-left: -110px;
982 982 float: left;
983 983 clear: left;
984 984 }
985 985 }
986 986
987 987 .load-more-commits {
988 988 text-align: center;
989 989 }
990 990 .load-more-commits:hover {
991 991 background-color: @grey7;
992 992 }
993 993 .load-more-commits {
994 994 a {
995 995 display: block;
996 996 }
997 997 }
998 998 }
999 999
1000 1000 #filter_changelog {
1001 1001 float: left;
1002 1002 }
1003 1003
1004 1004
1005 1005 //--- THEME ------------------//
1006 1006
1007 1007 #logo {
1008 1008 float: left;
1009 1009 margin: 9px 0 0 0;
1010 1010
1011 1011 .header {
1012 1012 background-color: transparent;
1013 1013 }
1014 1014
1015 1015 a {
1016 1016 display: inline-block;
1017 1017 }
1018 1018
1019 1019 img {
1020 1020 height:30px;
1021 1021 }
1022 1022 }
1023 1023
1024 1024 .logo-wrapper {
1025 1025 float:left;
1026 1026 }
1027 1027
1028 1028 .branding{
1029 1029 float: left;
1030 1030 padding: 9px 2px;
1031 1031 line-height: 1em;
1032 1032 font-size: @navigation-fontsize;
1033 1033 }
1034 1034
1035 1035 img {
1036 1036 border: none;
1037 1037 outline: none;
1038 1038 }
1039 1039 user-profile-header
1040 1040 label {
1041 1041
1042 1042 input[type="checkbox"] {
1043 1043 margin-right: 1em;
1044 1044 }
1045 1045 input[type="radio"] {
1046 1046 margin-right: 1em;
1047 1047 }
1048 1048 }
1049 1049
1050 1050 .flag_status {
1051 1051 margin: 2px 8px 6px 2px;
1052 1052 &.under_review {
1053 1053 .circle(5px, @alert3);
1054 1054 }
1055 1055 &.approved {
1056 1056 .circle(5px, @alert1);
1057 1057 }
1058 1058 &.rejected,
1059 1059 &.forced_closed{
1060 1060 .circle(5px, @alert2);
1061 1061 }
1062 1062 &.not_reviewed {
1063 1063 .circle(5px, @grey5);
1064 1064 }
1065 1065 }
1066 1066
1067 1067 .flag_status_comment_box {
1068 1068 margin: 5px 6px 0px 2px;
1069 1069 }
1070 1070 .test_pattern_preview {
1071 1071 margin: @space 0;
1072 1072
1073 1073 p {
1074 1074 margin-bottom: 0;
1075 1075 border-bottom: @border-thickness solid @border-default-color;
1076 1076 color: @grey3;
1077 1077 }
1078 1078
1079 1079 .btn {
1080 1080 margin-bottom: @padding;
1081 1081 }
1082 1082 }
1083 1083 #test_pattern_result {
1084 1084 display: none;
1085 1085 &:extend(pre);
1086 1086 padding: .9em;
1087 1087 color: @grey3;
1088 1088 background-color: @grey7;
1089 1089 border-right: @border-thickness solid @border-default-color;
1090 1090 border-bottom: @border-thickness solid @border-default-color;
1091 1091 border-left: @border-thickness solid @border-default-color;
1092 1092 }
1093 1093
1094 1094 #repo_vcs_settings {
1095 1095 #inherit_overlay_vcs_default {
1096 1096 display: none;
1097 1097 }
1098 1098 #inherit_overlay_vcs_custom {
1099 1099 display: custom;
1100 1100 }
1101 1101 &.inherited {
1102 1102 #inherit_overlay_vcs_default {
1103 1103 display: block;
1104 1104 }
1105 1105 #inherit_overlay_vcs_custom {
1106 1106 display: none;
1107 1107 }
1108 1108 }
1109 1109 }
1110 1110
1111 1111 .issue-tracker-link {
1112 1112 color: @rcblue;
1113 1113 }
1114 1114
1115 1115 // Issue Tracker Table Show/Hide
1116 1116 #repo_issue_tracker {
1117 1117 #inherit_overlay {
1118 1118 display: none;
1119 1119 }
1120 1120 #custom_overlay {
1121 1121 display: custom;
1122 1122 }
1123 1123 &.inherited {
1124 1124 #inherit_overlay {
1125 1125 display: block;
1126 1126 }
1127 1127 #custom_overlay {
1128 1128 display: none;
1129 1129 }
1130 1130 }
1131 1131 }
1132 1132 table.issuetracker {
1133 1133 &.readonly {
1134 1134 tr, td {
1135 1135 color: @grey3;
1136 1136 }
1137 1137 }
1138 1138 .edit {
1139 1139 display: none;
1140 1140 }
1141 1141 .editopen {
1142 1142 .edit {
1143 1143 display: inline;
1144 1144 }
1145 1145 .entry {
1146 1146 display: none;
1147 1147 }
1148 1148 }
1149 1149 tr td.td-action {
1150 1150 min-width: 117px;
1151 1151 }
1152 1152 td input {
1153 1153 max-width: none;
1154 1154 min-width: 30px;
1155 1155 width: 80%;
1156 1156 }
1157 1157 .issuetracker_pref input {
1158 1158 width: 40%;
1159 1159 }
1160 1160 input.edit_issuetracker_update {
1161 1161 margin-right: 0;
1162 1162 width: auto;
1163 1163 }
1164 1164 }
1165 1165
1166 1166 table.integrations {
1167 1167 .td-icon {
1168 1168 width: 20px;
1169 1169 .integration-icon {
1170 1170 height: 20px;
1171 1171 width: 20px;
1172 1172 }
1173 1173 }
1174 1174 }
1175 1175
1176 1176 .integrations {
1177 1177 a.integration-box {
1178 1178 color: @text-color;
1179 1179 &:hover {
1180 1180 .panel {
1181 1181 background: #fbfbfb;
1182 1182 }
1183 1183 }
1184 1184 .integration-icon {
1185 1185 width: 30px;
1186 1186 height: 30px;
1187 1187 margin-right: 20px;
1188 1188 float: left;
1189 1189 }
1190 1190
1191 1191 .panel-body {
1192 1192 padding: 10px;
1193 1193 }
1194 1194 .panel {
1195 1195 margin-bottom: 10px;
1196 1196 }
1197 1197 h2 {
1198 1198 display: inline-block;
1199 1199 margin: 0;
1200 1200 min-width: 140px;
1201 1201 }
1202 1202 }
1203 1203 a.integration-box.dummy-integration {
1204 1204 color: @grey4
1205 1205 }
1206 1206 }
1207 1207
1208 1208 //Permissions Settings
1209 1209 #add_perm {
1210 1210 margin: 0 0 @padding;
1211 1211 cursor: pointer;
1212 1212 }
1213 1213
1214 1214 .perm_ac {
1215 1215 input {
1216 1216 width: 95%;
1217 1217 }
1218 1218 }
1219 1219
1220 1220 .autocomplete-suggestions {
1221 1221 width: auto !important; // overrides autocomplete.js
1222 1222 margin: 0;
1223 1223 border: @border-thickness solid @rcblue;
1224 1224 border-radius: @border-radius;
1225 1225 color: @rcblue;
1226 1226 background-color: white;
1227 1227 }
1228 1228 .autocomplete-selected {
1229 1229 background: #F0F0F0;
1230 1230 }
1231 1231 .ac-container-wrap {
1232 1232 margin: 0;
1233 1233 padding: 8px;
1234 1234 border-bottom: @border-thickness solid @rclightblue;
1235 1235 list-style-type: none;
1236 1236 cursor: pointer;
1237 1237
1238 1238 &:hover {
1239 1239 background-color: @rclightblue;
1240 1240 }
1241 1241
1242 1242 img {
1243 1243 height: @gravatar-size;
1244 1244 width: @gravatar-size;
1245 1245 margin-right: 1em;
1246 1246 }
1247 1247
1248 1248 strong {
1249 1249 font-weight: normal;
1250 1250 }
1251 1251 }
1252 1252
1253 1253 // Settings Dropdown
1254 1254 .user-menu .container {
1255 1255 padding: 0 4px;
1256 1256 margin: 0;
1257 1257 }
1258 1258
1259 1259 .user-menu .gravatar {
1260 1260 cursor: pointer;
1261 1261 }
1262 1262
1263 1263 .codeblock {
1264 1264 margin-bottom: @padding;
1265 1265 clear: both;
1266 1266
1267 1267 .stats{
1268 1268 overflow: hidden;
1269 1269 }
1270 1270
1271 1271 .message{
1272 1272 textarea{
1273 1273 margin: 0;
1274 1274 }
1275 1275 }
1276 1276
1277 1277 .code-header {
1278 1278 .stats {
1279 1279 line-height: 2em;
1280 1280
1281 1281 .revision_id {
1282 1282 margin-left: 0;
1283 1283 }
1284 1284 .buttons {
1285 1285 padding-right: 0;
1286 1286 }
1287 1287 }
1288 1288
1289 1289 .item{
1290 1290 margin-right: 0.5em;
1291 1291 }
1292 1292 }
1293 1293
1294 1294 #editor_container{
1295 1295 position: relative;
1296 1296 margin: @padding;
1297 1297 }
1298 1298 }
1299 1299
1300 1300 #file_history_container {
1301 1301 display: none;
1302 1302 }
1303 1303
1304 1304 .file-history-inner {
1305 1305 margin-bottom: 10px;
1306 1306 }
1307 1307
1308 1308 // Pull Requests
1309 1309 .summary-details {
1310 1310 width: 72%;
1311 1311 }
1312 1312 .pr-summary {
1313 1313 border-bottom: @border-thickness solid @grey5;
1314 1314 margin-bottom: @space;
1315 1315 }
1316 1316 .reviewers-title {
1317 1317 width: 25%;
1318 1318 min-width: 200px;
1319 1319 }
1320 1320 .reviewers {
1321 1321 width: 25%;
1322 1322 min-width: 200px;
1323 1323 }
1324 1324 .reviewers ul li {
1325 1325 position: relative;
1326 1326 width: 100%;
1327 1327 padding-bottom: 8px;
1328 1328 }
1329 1329
1330 1330 .reviewer_entry {
1331 1331 min-height: 55px;
1332 1332 }
1333 1333
1334 1334 .reviewers_member {
1335 1335 width: 100%;
1336 1336 overflow: auto;
1337 1337 }
1338 1338 .reviewer_reason {
1339 1339 padding-left: 20px;
1340 1340 line-height: 1.5em;
1341 1341 }
1342 1342 .reviewer_status {
1343 1343 display: inline-block;
1344 1344 vertical-align: top;
1345 1345 width: 25px;
1346 1346 min-width: 25px;
1347 1347 height: 1.2em;
1348 1348 margin-top: 3px;
1349 1349 line-height: 1em;
1350 1350 }
1351 1351
1352 1352 .reviewer_name {
1353 1353 display: inline-block;
1354 1354 max-width: 83%;
1355 1355 padding-right: 20px;
1356 1356 vertical-align: middle;
1357 1357 line-height: 1;
1358 1358
1359 1359 .rc-user {
1360 1360 min-width: 0;
1361 1361 margin: -2px 1em 0 0;
1362 1362 }
1363 1363
1364 1364 .reviewer {
1365 1365 float: left;
1366 1366 }
1367 1367 }
1368 1368
1369 1369 .reviewer_member_mandatory {
1370 1370 position: absolute;
1371 1371 left: 15px;
1372 1372 top: 8px;
1373 1373 width: 16px;
1374 1374 font-size: 11px;
1375 1375 margin: 0;
1376 1376 padding: 0;
1377 1377 color: black;
1378 1378 }
1379 1379
1380 1380 .reviewer_member_mandatory_remove,
1381 1381 .reviewer_member_remove {
1382 1382 position: absolute;
1383 1383 right: 0;
1384 1384 top: 0;
1385 1385 width: 16px;
1386 1386 margin-bottom: 10px;
1387 1387 padding: 0;
1388 1388 color: black;
1389 1389 }
1390 1390
1391 1391 .reviewer_member_mandatory_remove {
1392 1392 color: @grey4;
1393 1393 }
1394 1394
1395 1395 .reviewer_member_status {
1396 1396 margin-top: 5px;
1397 1397 }
1398 1398 .pr-summary #summary{
1399 1399 width: 100%;
1400 1400 }
1401 1401 .pr-summary .action_button:hover {
1402 1402 border: 0;
1403 1403 cursor: pointer;
1404 1404 }
1405 1405 .pr-details-title {
1406 1406 padding-bottom: 8px;
1407 1407 border-bottom: @border-thickness solid @grey5;
1408 1408
1409 1409 .action_button.disabled {
1410 1410 color: @grey4;
1411 1411 cursor: inherit;
1412 1412 }
1413 1413 .action_button {
1414 1414 color: @rcblue;
1415 1415 }
1416 1416 }
1417 1417 .pr-details-content {
1418 1418 margin-top: @textmargin;
1419 1419 margin-bottom: @textmargin;
1420 1420 }
1421 .pr-description {
1422 white-space:pre-wrap;
1423 }
1424 1421
1425 1422 .pr-reviewer-rules {
1426 1423 padding: 10px 0px 20px 0px;
1427 1424 }
1428 1425
1429 1426 .group_members {
1430 1427 margin-top: 0;
1431 1428 padding: 0;
1432 1429 list-style: outside none none;
1433 1430
1434 1431 img {
1435 1432 height: @gravatar-size;
1436 1433 width: @gravatar-size;
1437 1434 margin-right: .5em;
1438 1435 margin-left: 3px;
1439 1436 }
1440 1437
1441 1438 .to-delete {
1442 1439 .user {
1443 1440 text-decoration: line-through;
1444 1441 }
1445 1442 }
1446 1443 }
1447 1444
1448 1445 .compare_view_commits_title {
1449 1446 .disabled {
1450 1447 cursor: inherit;
1451 1448 &:hover{
1452 1449 background-color: inherit;
1453 1450 color: inherit;
1454 1451 }
1455 1452 }
1456 1453 }
1457 1454
1458 1455 .subtitle-compare {
1459 1456 margin: -15px 0px 0px 0px;
1460 1457 }
1461 1458
1462 1459 .comments-summary-td {
1463 1460 border-top: 1px dashed @grey5;
1464 1461 }
1465 1462
1466 1463 // new entry in group_members
1467 1464 .td-author-new-entry {
1468 1465 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1469 1466 }
1470 1467
1471 1468 .usergroup_member_remove {
1472 1469 width: 16px;
1473 1470 margin-bottom: 10px;
1474 1471 padding: 0;
1475 1472 color: black !important;
1476 1473 cursor: pointer;
1477 1474 }
1478 1475
1479 1476 .reviewer_ac .ac-input {
1480 1477 width: 92%;
1481 1478 margin-bottom: 1em;
1482 1479 }
1483 1480
1484 1481 .compare_view_commits tr{
1485 1482 height: 20px;
1486 1483 }
1487 1484 .compare_view_commits td {
1488 1485 vertical-align: top;
1489 1486 padding-top: 10px;
1490 1487 }
1491 1488 .compare_view_commits .author {
1492 1489 margin-left: 5px;
1493 1490 }
1494 1491
1495 1492 .compare_view_commits {
1496 1493 .color-a {
1497 1494 color: @alert1;
1498 1495 }
1499 1496
1500 1497 .color-c {
1501 1498 color: @color3;
1502 1499 }
1503 1500
1504 1501 .color-r {
1505 1502 color: @color5;
1506 1503 }
1507 1504
1508 1505 .color-a-bg {
1509 1506 background-color: @alert1;
1510 1507 }
1511 1508
1512 1509 .color-c-bg {
1513 1510 background-color: @alert3;
1514 1511 }
1515 1512
1516 1513 .color-r-bg {
1517 1514 background-color: @alert2;
1518 1515 }
1519 1516
1520 1517 .color-a-border {
1521 1518 border: 1px solid @alert1;
1522 1519 }
1523 1520
1524 1521 .color-c-border {
1525 1522 border: 1px solid @alert3;
1526 1523 }
1527 1524
1528 1525 .color-r-border {
1529 1526 border: 1px solid @alert2;
1530 1527 }
1531 1528
1532 1529 .commit-change-indicator {
1533 1530 width: 15px;
1534 1531 height: 15px;
1535 1532 position: relative;
1536 1533 left: 15px;
1537 1534 }
1538 1535
1539 1536 .commit-change-content {
1540 1537 text-align: center;
1541 1538 vertical-align: middle;
1542 1539 line-height: 15px;
1543 1540 }
1544 1541 }
1545 1542
1546 1543 .compare_view_filepath {
1547 1544 color: @grey1;
1548 1545 }
1549 1546
1550 1547 .show_more {
1551 1548 display: inline-block;
1552 1549 position: relative;
1553 1550 vertical-align: middle;
1554 1551 width: 4px;
1555 1552 height: @basefontsize;
1556 1553
1557 1554 &:after {
1558 1555 content: "\00A0\25BE";
1559 1556 display: inline-block;
1560 1557 width:10px;
1561 1558 line-height: 5px;
1562 1559 font-size: 12px;
1563 1560 cursor: pointer;
1564 1561 }
1565 1562 }
1566 1563
1567 1564 .journal_more .show_more {
1568 1565 display: inline;
1569 1566
1570 1567 &:after {
1571 1568 content: none;
1572 1569 }
1573 1570 }
1574 1571
1575 1572 .open .show_more:after,
1576 1573 .select2-dropdown-open .show_more:after {
1577 1574 .rotate(180deg);
1578 1575 margin-left: 4px;
1579 1576 }
1580 1577
1581 1578
1582 1579 .compare_view_commits .collapse_commit:after {
1583 1580 cursor: pointer;
1584 1581 content: "\00A0\25B4";
1585 1582 margin-left: -3px;
1586 1583 font-size: 17px;
1587 1584 color: @grey4;
1588 1585 }
1589 1586
1590 1587 .diff_links {
1591 1588 margin-left: 8px;
1592 1589 }
1593 1590
1594 1591 div.ancestor {
1595 1592 margin: -30px 0px;
1596 1593 }
1597 1594
1598 1595 .cs_icon_td input[type="checkbox"] {
1599 1596 display: none;
1600 1597 }
1601 1598
1602 1599 .cs_icon_td .expand_file_icon:after {
1603 1600 cursor: pointer;
1604 1601 content: "\00A0\25B6";
1605 1602 font-size: 12px;
1606 1603 color: @grey4;
1607 1604 }
1608 1605
1609 1606 .cs_icon_td .collapse_file_icon:after {
1610 1607 cursor: pointer;
1611 1608 content: "\00A0\25BC";
1612 1609 font-size: 12px;
1613 1610 color: @grey4;
1614 1611 }
1615 1612
1616 1613 /*new binary
1617 1614 NEW_FILENODE = 1
1618 1615 DEL_FILENODE = 2
1619 1616 MOD_FILENODE = 3
1620 1617 RENAMED_FILENODE = 4
1621 1618 COPIED_FILENODE = 5
1622 1619 CHMOD_FILENODE = 6
1623 1620 BIN_FILENODE = 7
1624 1621 */
1625 1622 .cs_files_expand {
1626 1623 font-size: @basefontsize + 5px;
1627 1624 line-height: 1.8em;
1628 1625 float: right;
1629 1626 }
1630 1627
1631 1628 .cs_files_expand span{
1632 1629 color: @rcblue;
1633 1630 cursor: pointer;
1634 1631 }
1635 1632 .cs_files {
1636 1633 clear: both;
1637 1634 padding-bottom: @padding;
1638 1635
1639 1636 .cur_cs {
1640 1637 margin: 10px 2px;
1641 1638 font-weight: bold;
1642 1639 }
1643 1640
1644 1641 .node {
1645 1642 float: left;
1646 1643 }
1647 1644
1648 1645 .changes {
1649 1646 float: right;
1650 1647 color: white;
1651 1648 font-size: @basefontsize - 4px;
1652 1649 margin-top: 4px;
1653 1650 opacity: 0.6;
1654 1651 filter: Alpha(opacity=60); /* IE8 and earlier */
1655 1652
1656 1653 .added {
1657 1654 background-color: @alert1;
1658 1655 float: left;
1659 1656 text-align: center;
1660 1657 }
1661 1658
1662 1659 .deleted {
1663 1660 background-color: @alert2;
1664 1661 float: left;
1665 1662 text-align: center;
1666 1663 }
1667 1664
1668 1665 .bin {
1669 1666 background-color: @alert1;
1670 1667 text-align: center;
1671 1668 }
1672 1669
1673 1670 /*new binary*/
1674 1671 .bin.bin1 {
1675 1672 background-color: @alert1;
1676 1673 text-align: center;
1677 1674 }
1678 1675
1679 1676 /*deleted binary*/
1680 1677 .bin.bin2 {
1681 1678 background-color: @alert2;
1682 1679 text-align: center;
1683 1680 }
1684 1681
1685 1682 /*mod binary*/
1686 1683 .bin.bin3 {
1687 1684 background-color: @grey2;
1688 1685 text-align: center;
1689 1686 }
1690 1687
1691 1688 /*rename file*/
1692 1689 .bin.bin4 {
1693 1690 background-color: @alert4;
1694 1691 text-align: center;
1695 1692 }
1696 1693
1697 1694 /*copied file*/
1698 1695 .bin.bin5 {
1699 1696 background-color: @alert4;
1700 1697 text-align: center;
1701 1698 }
1702 1699
1703 1700 /*chmod file*/
1704 1701 .bin.bin6 {
1705 1702 background-color: @grey2;
1706 1703 text-align: center;
1707 1704 }
1708 1705 }
1709 1706 }
1710 1707
1711 1708 .cs_files .cs_added, .cs_files .cs_A,
1712 1709 .cs_files .cs_added, .cs_files .cs_M,
1713 1710 .cs_files .cs_added, .cs_files .cs_D {
1714 1711 height: 16px;
1715 1712 padding-right: 10px;
1716 1713 margin-top: 7px;
1717 1714 text-align: left;
1718 1715 }
1719 1716
1720 1717 .cs_icon_td {
1721 1718 min-width: 16px;
1722 1719 width: 16px;
1723 1720 }
1724 1721
1725 1722 .pull-request-merge {
1726 1723 border: 1px solid @grey5;
1727 1724 padding: 10px 0px 20px;
1728 1725 margin-top: 10px;
1729 1726 margin-bottom: 20px;
1730 1727 }
1731 1728
1732 1729 .pull-request-merge ul {
1733 1730 padding: 0px 0px;
1734 1731 }
1735 1732
1736 1733 .pull-request-merge li:before{
1737 1734 content:none;
1738 1735 }
1739 1736
1740 1737 .pull-request-merge .pull-request-wrap {
1741 1738 height: auto;
1742 1739 padding: 0px 0px;
1743 1740 text-align: right;
1744 1741 }
1745 1742
1746 1743 .pull-request-merge span {
1747 1744 margin-right: 5px;
1748 1745 }
1749 1746
1750 1747 .pull-request-merge-actions {
1751 1748 min-height: 30px;
1752 1749 padding: 0px 0px;
1753 1750 }
1754 1751
1755 1752 .pull-request-merge-info {
1756 1753 padding: 0px 5px 5px 0px;
1757 1754 }
1758 1755
1759 1756 .merge-status {
1760 1757 margin-right: 5px;
1761 1758 }
1762 1759
1763 1760 .merge-message {
1764 1761 font-size: 1.2em
1765 1762 }
1766 1763
1767 1764 .merge-message.success i,
1768 1765 .merge-icon.success i {
1769 1766 color:@alert1;
1770 1767 }
1771 1768
1772 1769 .merge-message.warning i,
1773 1770 .merge-icon.warning i {
1774 1771 color: @alert3;
1775 1772 }
1776 1773
1777 1774 .merge-message.error i,
1778 1775 .merge-icon.error i {
1779 1776 color:@alert2;
1780 1777 }
1781 1778
1782 1779 .pr-versions {
1783 1780 font-size: 1.1em;
1784 1781
1785 1782 table {
1786 1783 padding: 0px 5px;
1787 1784 }
1788 1785
1789 1786 td {
1790 1787 line-height: 15px;
1791 1788 }
1792 1789
1793 1790 .flag_status {
1794 1791 margin: 0;
1795 1792 }
1796 1793
1797 1794 .compare-radio-button {
1798 1795 position: relative;
1799 1796 top: -3px;
1800 1797 }
1801 1798 }
1802 1799
1803 1800
1804 1801 #close_pull_request {
1805 1802 margin-right: 0px;
1806 1803 }
1807 1804
1808 1805 .empty_data {
1809 1806 color: @grey4;
1810 1807 }
1811 1808
1812 1809 #changeset_compare_view_content {
1813 1810 margin-bottom: @space;
1814 1811 clear: both;
1815 1812 width: 100%;
1816 1813 box-sizing: border-box;
1817 1814 .border-radius(@border-radius);
1818 1815
1819 1816 .help-block {
1820 1817 margin: @padding 0;
1821 1818 color: @text-color;
1822 1819 &.pre-formatting {
1823 1820 white-space: pre;
1824 1821 }
1825 1822 }
1826 1823
1827 1824 .empty_data {
1828 1825 margin: @padding 0;
1829 1826 }
1830 1827
1831 1828 .alert {
1832 1829 margin-bottom: @space;
1833 1830 }
1834 1831 }
1835 1832
1836 1833 .table_disp {
1837 1834 .status {
1838 1835 width: auto;
1839 1836
1840 1837 .flag_status {
1841 1838 float: left;
1842 1839 }
1843 1840 }
1844 1841 }
1845 1842
1846 1843
1847 1844 .creation_in_progress {
1848 1845 color: @grey4
1849 1846 }
1850 1847
1851 1848 .status_box_menu {
1852 1849 margin: 0;
1853 1850 }
1854 1851
1855 1852 .notification-table{
1856 1853 margin-bottom: @space;
1857 1854 display: table;
1858 1855 width: 100%;
1859 1856
1860 1857 .container{
1861 1858 display: table-row;
1862 1859
1863 1860 .notification-header{
1864 1861 border-bottom: @border-thickness solid @border-default-color;
1865 1862 }
1866 1863
1867 1864 .notification-subject{
1868 1865 display: table-cell;
1869 1866 }
1870 1867 }
1871 1868 }
1872 1869
1873 1870 // Notifications
1874 1871 .notification-header{
1875 1872 display: table;
1876 1873 width: 100%;
1877 1874 padding: floor(@basefontsize/2) 0;
1878 1875 line-height: 1em;
1879 1876
1880 1877 .desc, .delete-notifications, .read-notifications{
1881 1878 display: table-cell;
1882 1879 text-align: left;
1883 1880 }
1884 1881
1885 1882 .desc{
1886 1883 width: 1163px;
1887 1884 }
1888 1885
1889 1886 .delete-notifications, .read-notifications{
1890 1887 width: 35px;
1891 1888 min-width: 35px; //fixes when only one button is displayed
1892 1889 }
1893 1890 }
1894 1891
1895 1892 .notification-body {
1896 1893 .markdown-block,
1897 1894 .rst-block {
1898 1895 padding: @padding 0;
1899 1896 }
1900 1897
1901 1898 .notification-subject {
1902 1899 padding: @textmargin 0;
1903 1900 border-bottom: @border-thickness solid @border-default-color;
1904 1901 }
1905 1902 }
1906 1903
1907 1904
1908 1905 .notifications_buttons{
1909 1906 float: right;
1910 1907 }
1911 1908
1912 1909 #notification-status{
1913 1910 display: inline;
1914 1911 }
1915 1912
1916 1913 // Repositories
1917 1914
1918 1915 #summary.fields{
1919 1916 display: table;
1920 1917
1921 1918 .field{
1922 1919 display: table-row;
1923 1920
1924 1921 .label-summary{
1925 1922 display: table-cell;
1926 1923 min-width: @label-summary-minwidth;
1927 1924 padding-top: @padding/2;
1928 1925 padding-bottom: @padding/2;
1929 1926 padding-right: @padding/2;
1930 1927 }
1931 1928
1932 1929 .input{
1933 1930 display: table-cell;
1934 1931 padding: @padding/2;
1935 1932
1936 1933 input{
1937 1934 min-width: 29em;
1938 1935 padding: @padding/4;
1939 1936 }
1940 1937 }
1941 1938 .statistics, .downloads{
1942 1939 .disabled{
1943 1940 color: @grey4;
1944 1941 }
1945 1942 }
1946 1943 }
1947 1944 }
1948 1945
1949 1946 #summary{
1950 1947 width: 70%;
1951 1948 }
1952 1949
1953 1950
1954 1951 // Journal
1955 1952 .journal.title {
1956 1953 h5 {
1957 1954 float: left;
1958 1955 margin: 0;
1959 1956 width: 70%;
1960 1957 }
1961 1958
1962 1959 ul {
1963 1960 float: right;
1964 1961 display: inline-block;
1965 1962 margin: 0;
1966 1963 width: 30%;
1967 1964 text-align: right;
1968 1965
1969 1966 li {
1970 1967 display: inline;
1971 1968 font-size: @journal-fontsize;
1972 1969 line-height: 1em;
1973 1970
1974 1971 &:before { content: none; }
1975 1972 }
1976 1973 }
1977 1974 }
1978 1975
1979 1976 .filterexample {
1980 1977 position: absolute;
1981 1978 top: 95px;
1982 1979 left: @contentpadding;
1983 1980 color: @rcblue;
1984 1981 font-size: 11px;
1985 1982 font-family: @text-regular;
1986 1983 cursor: help;
1987 1984
1988 1985 &:hover {
1989 1986 color: @rcdarkblue;
1990 1987 }
1991 1988
1992 1989 @media (max-width:768px) {
1993 1990 position: relative;
1994 1991 top: auto;
1995 1992 left: auto;
1996 1993 display: block;
1997 1994 }
1998 1995 }
1999 1996
2000 1997
2001 1998 #journal{
2002 1999 margin-bottom: @space;
2003 2000
2004 2001 .journal_day{
2005 2002 margin-bottom: @textmargin/2;
2006 2003 padding-bottom: @textmargin/2;
2007 2004 font-size: @journal-fontsize;
2008 2005 border-bottom: @border-thickness solid @border-default-color;
2009 2006 }
2010 2007
2011 2008 .journal_container{
2012 2009 margin-bottom: @space;
2013 2010
2014 2011 .journal_user{
2015 2012 display: inline-block;
2016 2013 }
2017 2014 .journal_action_container{
2018 2015 display: block;
2019 2016 margin-top: @textmargin;
2020 2017
2021 2018 div{
2022 2019 display: inline;
2023 2020 }
2024 2021
2025 2022 div.journal_action_params{
2026 2023 display: block;
2027 2024 }
2028 2025
2029 2026 div.journal_repo:after{
2030 2027 content: "\A";
2031 2028 white-space: pre;
2032 2029 }
2033 2030
2034 2031 div.date{
2035 2032 display: block;
2036 2033 margin-bottom: @textmargin;
2037 2034 }
2038 2035 }
2039 2036 }
2040 2037 }
2041 2038
2042 2039 // Files
2043 2040 .edit-file-title {
2044 2041 border-bottom: @border-thickness solid @border-default-color;
2045 2042
2046 2043 .breadcrumbs {
2047 2044 margin-bottom: 0;
2048 2045 }
2049 2046 }
2050 2047
2051 2048 .edit-file-fieldset {
2052 2049 margin-top: @sidebarpadding;
2053 2050
2054 2051 .fieldset {
2055 2052 .left-label {
2056 2053 width: 13%;
2057 2054 }
2058 2055 .right-content {
2059 2056 width: 87%;
2060 2057 max-width: 100%;
2061 2058 }
2062 2059 .filename-label {
2063 2060 margin-top: 13px;
2064 2061 }
2065 2062 .commit-message-label {
2066 2063 margin-top: 4px;
2067 2064 }
2068 2065 .file-upload-input {
2069 2066 input {
2070 2067 display: none;
2071 2068 }
2072 2069 margin-top: 10px;
2073 2070 }
2074 2071 .file-upload-label {
2075 2072 margin-top: 10px;
2076 2073 }
2077 2074 p {
2078 2075 margin-top: 5px;
2079 2076 }
2080 2077
2081 2078 }
2082 2079 .custom-path-link {
2083 2080 margin-left: 5px;
2084 2081 }
2085 2082 #commit {
2086 2083 resize: vertical;
2087 2084 }
2088 2085 }
2089 2086
2090 2087 .delete-file-preview {
2091 2088 max-height: 250px;
2092 2089 }
2093 2090
2094 2091 .new-file,
2095 2092 #filter_activate,
2096 2093 #filter_deactivate {
2097 2094 float: left;
2098 2095 margin: 0 0 0 15px;
2099 2096 }
2100 2097
2101 2098 h3.files_location{
2102 2099 line-height: 2.4em;
2103 2100 }
2104 2101
2105 2102 .browser-nav {
2106 2103 display: table;
2107 2104 margin-bottom: @space;
2108 2105
2109 2106
2110 2107 .info_box {
2111 2108 display: inline-table;
2112 2109 height: 2.5em;
2113 2110
2114 2111 .browser-cur-rev, .info_box_elem {
2115 2112 display: table-cell;
2116 2113 vertical-align: middle;
2117 2114 }
2118 2115
2119 2116 .info_box_elem {
2120 2117 border-top: @border-thickness solid @rcblue;
2121 2118 border-bottom: @border-thickness solid @rcblue;
2122 2119
2123 2120 #at_rev, a {
2124 2121 padding: 0.6em 0.9em;
2125 2122 margin: 0;
2126 2123 .box-shadow(none);
2127 2124 border: 0;
2128 2125 height: 12px;
2129 2126 }
2130 2127
2131 2128 input#at_rev {
2132 2129 max-width: 50px;
2133 2130 text-align: right;
2134 2131 }
2135 2132
2136 2133 &.previous {
2137 2134 border: @border-thickness solid @rcblue;
2138 2135 .disabled {
2139 2136 color: @grey4;
2140 2137 cursor: not-allowed;
2141 2138 }
2142 2139 }
2143 2140
2144 2141 &.next {
2145 2142 border: @border-thickness solid @rcblue;
2146 2143 .disabled {
2147 2144 color: @grey4;
2148 2145 cursor: not-allowed;
2149 2146 }
2150 2147 }
2151 2148 }
2152 2149
2153 2150 .browser-cur-rev {
2154 2151
2155 2152 span{
2156 2153 margin: 0;
2157 2154 color: @rcblue;
2158 2155 height: 12px;
2159 2156 display: inline-block;
2160 2157 padding: 0.7em 1em ;
2161 2158 border: @border-thickness solid @rcblue;
2162 2159 margin-right: @padding;
2163 2160 }
2164 2161 }
2165 2162 }
2166 2163
2167 2164 .search_activate {
2168 2165 display: table-cell;
2169 2166 vertical-align: middle;
2170 2167
2171 2168 input, label{
2172 2169 margin: 0;
2173 2170 padding: 0;
2174 2171 }
2175 2172
2176 2173 input{
2177 2174 margin-left: @textmargin;
2178 2175 }
2179 2176
2180 2177 }
2181 2178 }
2182 2179
2183 2180 .browser-cur-rev{
2184 2181 margin-bottom: @textmargin;
2185 2182 }
2186 2183
2187 2184 #node_filter_box_loading{
2188 2185 .info_text;
2189 2186 }
2190 2187
2191 2188 .browser-search {
2192 2189 margin: -25px 0px 5px 0px;
2193 2190 }
2194 2191
2195 2192 .node-filter {
2196 2193 font-size: @repo-title-fontsize;
2197 2194 padding: 4px 0px 0px 0px;
2198 2195
2199 2196 .node-filter-path {
2200 2197 float: left;
2201 2198 color: @grey4;
2202 2199 }
2203 2200 .node-filter-input {
2204 2201 float: left;
2205 2202 margin: -2px 0px 0px 2px;
2206 2203 input {
2207 2204 padding: 2px;
2208 2205 border: none;
2209 2206 font-size: @repo-title-fontsize;
2210 2207 }
2211 2208 }
2212 2209 }
2213 2210
2214 2211
2215 2212 .browser-result{
2216 2213 td a{
2217 2214 margin-left: 0.5em;
2218 2215 display: inline-block;
2219 2216
2220 2217 em{
2221 2218 font-family: @text-bold;
2222 2219 }
2223 2220 }
2224 2221 }
2225 2222
2226 2223 .browser-highlight{
2227 2224 background-color: @grey5-alpha;
2228 2225 }
2229 2226
2230 2227
2231 2228 // Search
2232 2229
2233 2230 .search-form{
2234 2231 #q {
2235 2232 width: @search-form-width;
2236 2233 }
2237 2234 .fields{
2238 2235 margin: 0 0 @space;
2239 2236 }
2240 2237
2241 2238 label{
2242 2239 display: inline-block;
2243 2240 margin-right: @textmargin;
2244 2241 padding-top: 0.25em;
2245 2242 }
2246 2243
2247 2244
2248 2245 .results{
2249 2246 clear: both;
2250 2247 margin: 0 0 @padding;
2251 2248 }
2252 2249 }
2253 2250
2254 2251 div.search-feedback-items {
2255 2252 display: inline-block;
2256 2253 padding:0px 0px 0px 96px;
2257 2254 }
2258 2255
2259 2256 div.search-code-body {
2260 2257 background-color: #ffffff; padding: 5px 0 5px 10px;
2261 2258 pre {
2262 2259 .match { background-color: #faffa6;}
2263 2260 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2264 2261 }
2265 2262 }
2266 2263
2267 2264 .expand_commit.search {
2268 2265 .show_more.open {
2269 2266 height: auto;
2270 2267 max-height: none;
2271 2268 }
2272 2269 }
2273 2270
2274 2271 .search-results {
2275 2272
2276 2273 h2 {
2277 2274 margin-bottom: 0;
2278 2275 }
2279 2276 .codeblock {
2280 2277 border: none;
2281 2278 background: transparent;
2282 2279 }
2283 2280
2284 2281 .codeblock-header {
2285 2282 border: none;
2286 2283 background: transparent;
2287 2284 }
2288 2285
2289 2286 .code-body {
2290 2287 border: @border-thickness solid @border-default-color;
2291 2288 .border-radius(@border-radius);
2292 2289 }
2293 2290
2294 2291 .td-commit {
2295 2292 &:extend(pre);
2296 2293 border-bottom: @border-thickness solid @border-default-color;
2297 2294 }
2298 2295
2299 2296 .message {
2300 2297 height: auto;
2301 2298 max-width: 350px;
2302 2299 white-space: normal;
2303 2300 text-overflow: initial;
2304 2301 overflow: visible;
2305 2302
2306 2303 .match { background-color: #faffa6;}
2307 2304 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2308 2305 }
2309 2306
2310 2307 }
2311 2308
2312 2309 table.rctable td.td-search-results div {
2313 2310 max-width: 100%;
2314 2311 }
2315 2312
2316 2313 #tip-box, .tip-box{
2317 2314 padding: @menupadding/2;
2318 2315 display: block;
2319 2316 border: @border-thickness solid @border-highlight-color;
2320 2317 .border-radius(@border-radius);
2321 2318 background-color: white;
2322 2319 z-index: 99;
2323 2320 white-space: pre-wrap;
2324 2321 }
2325 2322
2326 2323 #linktt {
2327 2324 width: 79px;
2328 2325 }
2329 2326
2330 2327 #help_kb .modal-content{
2331 2328 max-width: 750px;
2332 2329 margin: 10% auto;
2333 2330
2334 2331 table{
2335 2332 td,th{
2336 2333 border-bottom: none;
2337 2334 line-height: 2.5em;
2338 2335 }
2339 2336 th{
2340 2337 padding-bottom: @textmargin/2;
2341 2338 }
2342 2339 td.keys{
2343 2340 text-align: center;
2344 2341 }
2345 2342 }
2346 2343
2347 2344 .block-left{
2348 2345 width: 45%;
2349 2346 margin-right: 5%;
2350 2347 }
2351 2348 .modal-footer{
2352 2349 clear: both;
2353 2350 }
2354 2351 .key.tag{
2355 2352 padding: 0.5em;
2356 2353 background-color: @rcblue;
2357 2354 color: white;
2358 2355 border-color: @rcblue;
2359 2356 .box-shadow(none);
2360 2357 }
2361 2358 }
2362 2359
2363 2360
2364 2361
2365 2362 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2366 2363
2367 2364 @import 'statistics-graph';
2368 2365 @import 'tables';
2369 2366 @import 'forms';
2370 2367 @import 'diff';
2371 2368 @import 'summary';
2372 2369 @import 'navigation';
2373 2370
2374 2371 //--- SHOW/HIDE SECTIONS --//
2375 2372
2376 2373 .btn-collapse {
2377 2374 float: right;
2378 2375 text-align: right;
2379 2376 font-family: @text-light;
2380 2377 font-size: @basefontsize;
2381 2378 cursor: pointer;
2382 2379 border: none;
2383 2380 color: @rcblue;
2384 2381 }
2385 2382
2386 2383 table.rctable,
2387 2384 table.dataTable {
2388 2385 .btn-collapse {
2389 2386 float: right;
2390 2387 text-align: right;
2391 2388 }
2392 2389 }
2393 2390
2394 2391
2395 2392 // TODO: johbo: Fix for IE10, this avoids that we see a border
2396 2393 // and padding around checkboxes and radio boxes. Move to the right place,
2397 2394 // or better: Remove this once we did the form refactoring.
2398 2395 input[type=checkbox],
2399 2396 input[type=radio] {
2400 2397 padding: 0;
2401 2398 border: none;
2402 2399 }
2403 2400
2404 2401 .toggle-ajax-spinner{
2405 2402 height: 16px;
2406 2403 width: 16px;
2407 2404 }
2405
2406
2407 .markup-form .clearfix {
2408 .border-radius(@border-radius);
2409 margin: 0px;
2410 }
2411
2412 .markup-form-area {
2413 padding: 8px 12px;
2414 border: 1px solid @grey4;
2415 .border-radius(@border-radius);
2416 }
2417
2418 .markup-form-area-header .nav-links {
2419 display: flex;
2420 flex-flow: row wrap;
2421 -webkit-flex-flow: row wrap;
2422 width: 100%;
2423 }
2424
2425 .markup-form-area-footer {
2426 display: flex;
2427 }
2428
2429 .markup-form-area-footer .toolbar {
2430
2431 }
2432
2433 // markup Form
2434 div.markup-form {
2435 margin-top: 20px;
2436 }
2437
2438 .markup-form strong {
2439 display: block;
2440 margin-bottom: 15px;
2441 }
2442
2443 .markup-form textarea {
2444 width: 100%;
2445 height: 100px;
2446 font-family: 'Monaco', 'Courier', 'Courier New', monospace;
2447 }
2448
2449 form.markup-form {
2450 margin-top: 10px;
2451 margin-left: 10px;
2452 }
2453
2454 .markup-form .comment-block-ta,
2455 .markup-form .preview-box {
2456 .border-radius(@border-radius);
2457 .box-sizing(border-box);
2458 background-color: white;
2459 }
2460
2461 .markup-form .preview-box.unloaded {
2462 height: 50px;
2463 text-align: center;
2464 padding: 20px;
2465 background-color: white;
2466 }
@@ -1,321 +1,325 b''
1 1
2 2 /******************************************************************************
3 3 * *
4 4 * DO NOT CHANGE THIS FILE MANUALLY *
5 5 * *
6 6 * *
7 7 * This file is automatically generated when the app starts up with *
8 8 * generate_js_files = true *
9 9 * *
10 10 * To add a route here pass jsroute=True to the route definition in the app *
11 11 * *
12 12 ******************************************************************************/
13 13 function registerRCRoutes() {
14 14 // routes registration
15 15 pyroutes.register('favicon', '/favicon.ico', []);
16 16 pyroutes.register('robots', '/robots.txt', []);
17 17 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
18 18 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
19 19 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
20 20 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
21 21 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
22 22 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
23 23 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
24 24 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
25 25 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
26 26 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
27 27 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
28 28 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
29 29 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
30 30 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
31 31 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
32 32 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
33 33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
34 34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
35 35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
36 36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
37 37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
38 38 pyroutes.register('admin_home', '/_admin', []);
39 39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
40 40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
41 41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
42 42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
43 43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
44 44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
45 45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
46 46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
47 47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
48 48 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
49 49 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
50 50 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
51 51 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
52 52 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
53 53 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
54 54 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
55 55 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
56 56 pyroutes.register('admin_settings', '/_admin/settings', []);
57 57 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
58 58 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
59 59 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
60 60 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
61 61 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
62 62 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
63 63 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
64 64 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
65 65 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
66 66 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
67 67 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
68 68 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
69 69 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
70 70 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
71 71 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
72 72 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
73 73 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
74 74 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
75 75 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
76 76 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
77 77 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
78 78 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
79 pyroutes.register('admin_settings_automation', '/_admin/_admin/settings/automation', []);
79 80 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
80 81 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
81 82 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
82 83 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
83 84 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
84 85 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
85 86 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
86 87 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
87 88 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
88 89 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
89 90 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
90 91 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
91 92 pyroutes.register('users', '/_admin/users', []);
92 93 pyroutes.register('users_data', '/_admin/users_data', []);
93 94 pyroutes.register('users_create', '/_admin/users/create', []);
94 95 pyroutes.register('users_new', '/_admin/users/new', []);
95 96 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
96 97 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
97 98 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
98 99 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
99 100 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
100 101 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
101 102 pyroutes.register('user_force_password_reset', '/_admin/users/%(user_id)s/password_reset', ['user_id']);
102 103 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
103 104 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
104 105 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
105 106 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
106 107 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
107 108 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
108 109 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
109 110 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
110 111 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
111 112 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
112 113 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
113 114 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
114 115 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
115 116 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
116 117 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
117 118 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
118 119 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
119 120 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
120 121 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
121 122 pyroutes.register('user_groups', '/_admin/user_groups', []);
122 123 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
123 124 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
124 125 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
125 126 pyroutes.register('repos', '/_admin/repos', []);
126 127 pyroutes.register('repo_new', '/_admin/repos/new', []);
127 128 pyroutes.register('repo_create', '/_admin/repos/create', []);
128 129 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
129 130 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
130 131 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
131 132 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
132 133 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
133 134 pyroutes.register('channelstream_proxy', '/_channelstream', []);
134 135 pyroutes.register('login', '/_admin/login', []);
135 136 pyroutes.register('logout', '/_admin/logout', []);
136 137 pyroutes.register('register', '/_admin/register', []);
137 138 pyroutes.register('reset_password', '/_admin/password_reset', []);
138 139 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
139 140 pyroutes.register('home', '/', []);
140 141 pyroutes.register('user_autocomplete_data', '/_users', []);
141 142 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
142 143 pyroutes.register('repo_list_data', '/_repos', []);
143 144 pyroutes.register('goto_switcher_data', '/_goto_data', []);
145 pyroutes.register('markup_preview', '/_markup_preview', []);
144 146 pyroutes.register('journal', '/_admin/journal', []);
145 147 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
146 148 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
147 149 pyroutes.register('journal_public', '/_admin/public_journal', []);
148 150 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
149 151 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
150 152 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
151 153 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
152 154 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
153 155 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
154 156 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
155 157 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
156 158 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
157 159 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
158 160 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
159 161 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
160 162 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
161 163 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
162 164 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
163 165 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
164 166 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
165 167 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
166 168 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
167 169 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
168 170 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
169 171 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
170 172 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
171 173 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
172 174 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
173 175 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
174 176 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
175 177 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
176 178 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
177 179 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
178 180 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
179 181 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
180 182 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
181 183 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
182 184 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
183 185 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
184 186 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
185 187 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
186 188 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
187 189 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
188 190 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
189 191 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
190 192 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
191 193 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
192 194 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
193 195 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
194 196 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
195 197 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 198 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
197 199 pyroutes.register('repo_changelog_elements_file', '/%(repo_name)s/changelog_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 200 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
199 201 pyroutes.register('repo_compare', '/%(repo_name)s/compare/%(source_ref_type)s@%(source_ref)s...%(target_ref_type)s@%(target_ref)s', ['repo_name', 'source_ref_type', 'source_ref', 'target_ref_type', 'target_ref']);
200 202 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
201 203 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
202 204 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
203 205 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
204 206 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
205 207 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
206 208 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
207 209 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
208 210 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
209 211 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
210 212 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
211 213 pyroutes.register('pullrequest_repo_destinations', '/%(repo_name)s/pull-request/repo-destinations', ['repo_name']);
212 214 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
213 215 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
214 216 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
215 217 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
216 218 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
217 219 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
218 220 pyroutes.register('pullrequest_comment_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment/%(comment_id)s/delete', ['repo_name', 'pull_request_id', 'comment_id']);
219 221 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
220 222 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
221 223 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
222 224 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
223 225 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
224 226 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
227 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
225 228 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
226 229 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
227 230 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
228 231 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
229 232 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
230 233 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
231 234 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
232 235 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
233 236 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
234 237 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
235 238 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
236 239 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
237 240 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
238 241 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
239 242 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
240 243 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
241 244 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
242 245 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
243 246 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
244 247 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
245 248 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
246 249 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
247 250 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
248 251 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
249 252 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
250 253 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
251 254 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
252 255 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
253 256 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
254 257 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
255 258 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
256 259 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
257 260 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
258 261 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
259 262 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
260 263 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
261 264 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
262 265 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
263 266 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
264 267 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
265 268 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
266 269 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
267 270 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
268 271 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
269 272 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
270 273 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
271 274 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
272 275 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
273 276 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
274 277 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
275 278 pyroutes.register('search', '/_admin/search', []);
276 279 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
277 280 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
281 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
278 282 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
279 283 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
280 284 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
281 285 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
282 286 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
283 287 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
284 288 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
285 289 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
286 290 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
287 291 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
288 292 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
289 293 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
290 294 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
291 295 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
292 296 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
293 297 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
294 298 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
295 299 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
296 300 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
297 301 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
298 302 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
299 303 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
300 304 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
301 305 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
302 306 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
303 307 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
304 308 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
305 309 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
306 310 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
307 311 pyroutes.register('gists_show', '/_admin/gists', []);
308 312 pyroutes.register('gists_new', '/_admin/gists/new', []);
309 313 pyroutes.register('gists_create', '/_admin/gists/create', []);
310 314 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
311 315 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
312 316 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
313 317 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
314 318 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
315 319 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
316 320 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
317 321 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
318 322 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
319 323 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
320 324 pyroutes.register('apiv2', '/_admin/api', []);
321 325 }
@@ -1,595 +1,850 b''
1 1 // # Copyright (C) 2010-2018 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 /**
20 20 * Code Mirror
21 21 */
22 22 // global code-mirror logger;, to enable run
23 23 // Logger.get('CodeMirror').setLevel(Logger.DEBUG)
24 24
25 25 cmLog = Logger.get('CodeMirror');
26 26 cmLog.setLevel(Logger.OFF);
27 27
28 28
29 29 //global cache for inline forms
30 30 var userHintsCache = {};
31 31
32 32 // global timer, used to cancel async loading
33 33 var CodeMirrorLoadUserHintTimer;
34 34
35 35 var escapeRegExChars = function(value) {
36 36 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
37 37 };
38 38
39 39 /**
40 40 * Load hints from external source returns an array of objects in a format
41 41 * that hinting lib requires
42 42 * @returns {Array}
43 43 */
44 44 var CodeMirrorLoadUserHints = function(query, triggerHints) {
45 45 cmLog.debug('Loading mentions users via AJAX');
46 46 var _users = [];
47 47 $.ajax({
48 48 type: 'GET',
49 49 data: {query: query},
50 50 url: pyroutes.url('user_autocomplete_data'),
51 51 headers: {'X-PARTIAL-XHR': true},
52 52 async: true
53 53 })
54 54 .done(function(data) {
55 55 var tmpl = '<img class="gravatar" src="{0}"/>{1}';
56 56 $.each(data.suggestions, function(i) {
57 57 var userObj = data.suggestions[i];
58 58
59 59 if (userObj.username !== "default") {
60 60 _users.push({
61 61 text: userObj.username + " ",
62 62 org_text: userObj.username,
63 63 displayText: userObj.value_display, // search that field
64 64 // internal caches
65 65 _icon_link: userObj.icon_link,
66 66 _text: userObj.value_display,
67 67
68 68 render: function(elt, data, completion) {
69 69 var el = document.createElement('div');
70 70 el.className = "CodeMirror-hint-entry";
71 71 el.innerHTML = tmpl.format(
72 72 completion._icon_link, completion._text);
73 73 elt.appendChild(el);
74 74 }
75 75 });
76 76 }
77 77 });
78 78 cmLog.debug('Mention users loaded');
79 79 // set to global cache
80 80 userHintsCache[query] = _users;
81 81 triggerHints(userHintsCache[query]);
82 82 })
83 83 .fail(function(data, textStatus, xhr) {
84 84 alert("error processing request. \n" +
85 85 "Error code {0} ({1}).".format(data.status, data.statusText));
86 86 });
87 87 };
88 88
89 89 /**
90 90 * filters the results based on the current context
91 91 * @param users
92 92 * @param context
93 93 * @returns {Array}
94 94 */
95 95 var CodeMirrorFilterUsers = function(users, context) {
96 96 var MAX_LIMIT = 10;
97 97 var filtered_users = [];
98 98 var curWord = context.string;
99 99
100 100 cmLog.debug('Filtering users based on query:', curWord);
101 101 $.each(users, function(i) {
102 102 var match = users[i];
103 103 var searchText = match.displayText;
104 104
105 105 if (!curWord ||
106 106 searchText.toLowerCase().lastIndexOf(curWord) !== -1) {
107 107 // reset state
108 108 match._text = match.displayText;
109 109 if (curWord) {
110 110 // do highlighting
111 111 var pattern = '(' + escapeRegExChars(curWord) + ')';
112 112 match._text = searchText.replace(
113 113 new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
114 114 }
115 115
116 116 filtered_users.push(match);
117 117 }
118 118 // to not return to many results, use limit of filtered results
119 119 if (filtered_users.length > MAX_LIMIT) {
120 120 return false;
121 121 }
122 122 });
123 123
124 124 return filtered_users;
125 125 };
126 126
127 127 var CodeMirrorMentionHint = function(editor, callback, options) {
128 128 var cur = editor.getCursor();
129 129 var curLine = editor.getLine(cur.line).slice(0, cur.ch);
130 130
131 131 // match on @ +1char
132 132 var tokenMatch = new RegExp(
133 133 '(^@| @)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]*)$').exec(curLine);
134 134
135 135 var tokenStr = '';
136 136 if (tokenMatch !== null && tokenMatch.length > 0){
137 137 tokenStr = tokenMatch[0].strip();
138 138 } else {
139 139 // skip if we didn't match our token
140 140 return;
141 141 }
142 142
143 143 var context = {
144 144 start: (cur.ch - tokenStr.length) + 1,
145 145 end: cur.ch,
146 146 string: tokenStr.slice(1),
147 147 type: null
148 148 };
149 149
150 150 // case when we put the @sign in fron of a string,
151 151 // eg <@ we put it here>sometext then we need to prepend to text
152 152 if (context.end > cur.ch) {
153 153 context.start = context.start + 1; // we add to the @ sign
154 154 context.end = cur.ch; // don't eat front part just append
155 155 context.string = context.string.slice(1, cur.ch - context.start);
156 156 }
157 157
158 158 cmLog.debug('Mention context', context);
159 159
160 160 var triggerHints = function(userHints){
161 161 return callback({
162 162 list: CodeMirrorFilterUsers(userHints, context),
163 163 from: CodeMirror.Pos(cur.line, context.start),
164 164 to: CodeMirror.Pos(cur.line, context.end)
165 165 });
166 166 };
167 167
168 168 var queryBasedHintsCache = undefined;
169 169 // if we have something in the cache, try to fetch the query based cache
170 170 if (userHintsCache !== {}){
171 171 queryBasedHintsCache = userHintsCache[context.string];
172 172 }
173 173
174 174 if (queryBasedHintsCache !== undefined) {
175 175 cmLog.debug('Users loaded from cache');
176 176 triggerHints(queryBasedHintsCache);
177 177 } else {
178 178 // this takes care for async loading, and then displaying results
179 179 // and also propagates the userHintsCache
180 180 window.clearTimeout(CodeMirrorLoadUserHintTimer);
181 181 CodeMirrorLoadUserHintTimer = setTimeout(function() {
182 182 CodeMirrorLoadUserHints(context.string, triggerHints);
183 183 }, 300);
184 184 }
185 185 };
186 186
187 187 var CodeMirrorCompleteAfter = function(cm, pred) {
188 188 var options = {
189 189 completeSingle: false,
190 190 async: true,
191 191 closeOnUnfocus: true
192 192 };
193 193 var cur = cm.getCursor();
194 194 setTimeout(function() {
195 195 if (!cm.state.completionActive) {
196 196 cmLog.debug('Trigger mentions hinting');
197 197 CodeMirror.showHint(cm, CodeMirror.hint.mentions, options);
198 198 }
199 199 }, 100);
200 200
201 201 // tell CodeMirror we didn't handle the key
202 202 // trick to trigger on a char but still complete it
203 203 return CodeMirror.Pass;
204 204 };
205 205
206 206 var initCodeMirror = function(textAreadId, resetUrl, focus, options) {
207 207 var ta = $('#' + textAreadId).get(0);
208 208 if (focus === undefined) {
209 209 focus = true;
210 210 }
211 211
212 212 // default options
213 213 var codeMirrorOptions = {
214 214 mode: "null",
215 215 lineNumbers: true,
216 216 indentUnit: 4,
217 217 autofocus: focus
218 218 };
219 219
220 220 if (options !== undefined) {
221 221 // extend with custom options
222 222 codeMirrorOptions = $.extend(true, codeMirrorOptions, options);
223 223 }
224 224
225 225 var myCodeMirror = CodeMirror.fromTextArea(ta, codeMirrorOptions);
226 226
227 227 $('#reset').on('click', function(e) {
228 228 window.location = resetUrl;
229 229 });
230 230
231 231 return myCodeMirror;
232 232 };
233 233
234
235 var initMarkupCodeMirror = function(textAreadId, focus, options) {
236 var initialHeight = 100;
237
238 var ta = $(textAreadId).get(0);
239 if (focus === undefined) {
240 focus = true;
241 }
242
243 // default options
244 var codeMirrorOptions = {
245 lineNumbers: false,
246 indentUnit: 4,
247 viewportMargin: 30,
248 // this is a trick to trigger some logic behind codemirror placeholder
249 // it influences styling and behaviour.
250 placeholder: " ",
251 lineWrapping: true,
252 autofocus: focus
253 };
254
255 if (options !== undefined) {
256 // extend with custom options
257 codeMirrorOptions = $.extend(true, codeMirrorOptions, options);
258 }
259
260 var cm = CodeMirror.fromTextArea(ta, codeMirrorOptions);
261 cm.setSize(null, initialHeight);
262 cm.setOption("mode", DEFAULT_RENDERER);
263 CodeMirror.autoLoadMode(cm, DEFAULT_RENDERER); // load rst or markdown mode
264 cmLog.debug('Loading codemirror mode', DEFAULT_RENDERER);
265
266 // start listening on changes to make auto-expanded editor
267 cm.on("change", function(instance, changeObj) {
268 var height = initialHeight;
269 var lines = instance.lineCount();
270 if ( lines > 6 && lines < 20) {
271 height = "auto";
272 }
273 else if (lines >= 20){
274 zheight = 20*15;
275 }
276 instance.setSize(null, height);
277
278 // detect if the change was trigger by auto desc, or user input
279 var changeOrigin = changeObj.origin;
280
281 if (changeOrigin === "setValue") {
282 cmLog.debug('Change triggered by setValue');
283 }
284 else {
285 cmLog.debug('user triggered change !');
286 // set special marker to indicate user has created an input.
287 instance._userDefinedValue = true;
288 }
289
290 });
291
292 return cm;
293 };
294
295
234 296 var initCommentBoxCodeMirror = function(CommentForm, textAreaId, triggerActions){
235 297 var initialHeight = 100;
236 298
237 299 if (typeof userHintsCache === "undefined") {
238 300 userHintsCache = {};
239 301 cmLog.debug('Init empty cache for mentions');
240 302 }
241 303 if (!$(textAreaId).get(0)) {
242 304 cmLog.debug('Element for textarea not found', textAreaId);
243 305 return;
244 306 }
245 307 /**
246 308 * Filter action based on typed in text
247 309 * @param actions
248 310 * @param context
249 311 * @returns {Array}
250 312 */
251 313
252 314 var filterActions = function(actions, context){
253 315
254 316 var MAX_LIMIT = 10;
255 317 var filtered_actions = [];
256 318 var curWord = context.string;
257 319
258 320 cmLog.debug('Filtering actions based on query:', curWord);
259 321 $.each(actions, function(i) {
260 322 var match = actions[i];
261 323 var searchText = match.searchText;
262 324
263 325 if (!curWord ||
264 326 searchText.toLowerCase().lastIndexOf(curWord) !== -1) {
265 327 // reset state
266 328 match._text = match.displayText;
267 329 if (curWord) {
268 330 // do highlighting
269 331 var pattern = '(' + escapeRegExChars(curWord) + ')';
270 332 match._text = searchText.replace(
271 333 new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
272 334 }
273 335
274 336 filtered_actions.push(match);
275 337 }
276 338 // to not return to many results, use limit of filtered results
277 339 if (filtered_actions.length > MAX_LIMIT) {
278 340 return false;
279 341 }
280 342 });
281 343
282 344 return filtered_actions;
283 345 };
284 346
285 347 var submitForm = function(cm, pred) {
286 348 $(cm.display.input.textarea.form).submit();
287 349 return CodeMirror.Pass;
288 350 };
289 351
290 352 var completeActions = function(actions){
291 353
292 354 var registeredActions = [];
293 355 var allActions = [
294 356 {
295 357 text: "approve",
296 358 searchText: "status approved",
297 359 displayText: _gettext('Set status to Approved'),
298 360 hint: function(CodeMirror, data, completion) {
299 361 CodeMirror.replaceRange("", completion.from || data.from,
300 362 completion.to || data.to, "complete");
301 363 $(CommentForm.statusChange).select2("val", 'approved').trigger('change');
302 364 },
303 365 render: function(elt, data, completion) {
304 366 var el = document.createElement('div');
305 367 el.className = "flag_status flag_status_comment_box approved pull-left";
306 368 elt.appendChild(el);
307 369
308 370 el = document.createElement('span');
309 371 el.innerHTML = completion.displayText;
310 372 elt.appendChild(el);
311 373 }
312 374 },
313 375 {
314 376 text: "reject",
315 377 searchText: "status rejected",
316 378 displayText: _gettext('Set status to Rejected'),
317 379 hint: function(CodeMirror, data, completion) {
318 380 CodeMirror.replaceRange("", completion.from || data.from,
319 381 completion.to || data.to, "complete");
320 382 $(CommentForm.statusChange).select2("val", 'rejected').trigger('change');
321 383 },
322 384 render: function(elt, data, completion) {
323 385 var el = document.createElement('div');
324 386 el.className = "flag_status flag_status_comment_box rejected pull-left";
325 387 elt.appendChild(el);
326 388
327 389 el = document.createElement('span');
328 390 el.innerHTML = completion.displayText;
329 391 elt.appendChild(el);
330 392 }
331 393 },
332 394 {
333 395 text: "as_todo",
334 396 searchText: "todo comment",
335 397 displayText: _gettext('TODO comment'),
336 398 hint: function(CodeMirror, data, completion) {
337 399 CodeMirror.replaceRange("", completion.from || data.from,
338 400 completion.to || data.to, "complete");
339 401
340 402 $(CommentForm.commentType).val('todo');
341 403 },
342 404 render: function(elt, data, completion) {
343 405 var el = document.createElement('div');
344 406 el.className = "pull-left";
345 407 elt.appendChild(el);
346 408
347 409 el = document.createElement('span');
348 410 el.innerHTML = completion.displayText;
349 411 elt.appendChild(el);
350 412 }
351 413 },
352 414 {
353 415 text: "as_note",
354 416 searchText: "note comment",
355 417 displayText: _gettext('Note Comment'),
356 418 hint: function(CodeMirror, data, completion) {
357 419 CodeMirror.replaceRange("", completion.from || data.from,
358 420 completion.to || data.to, "complete");
359 421
360 422 $(CommentForm.commentType).val('note');
361 423 },
362 424 render: function(elt, data, completion) {
363 425 var el = document.createElement('div');
364 426 el.className = "pull-left";
365 427 elt.appendChild(el);
366 428
367 429 el = document.createElement('span');
368 430 el.innerHTML = completion.displayText;
369 431 elt.appendChild(el);
370 432 }
371 433 }
372 434 ];
373 435
374 436 $.each(allActions, function(index, value){
375 437 var actionData = allActions[index];
376 438 if (actions.indexOf(actionData['text']) != -1) {
377 439 registeredActions.push(actionData);
378 440 }
379 441 });
380 442
381 443 return function(cm, pred) {
382 444 var cur = cm.getCursor();
383 445 var options = {
384 446 closeOnUnfocus: true,
385 447 registeredActions: registeredActions
386 448 };
387 449 setTimeout(function() {
388 450 if (!cm.state.completionActive) {
389 451 cmLog.debug('Trigger actions hinting');
390 452 CodeMirror.showHint(cm, CodeMirror.hint.actions, options);
391 453 }
392 454 }, 100);
393 455
394 456 // tell CodeMirror we didn't handle the key
395 457 // trick to trigger on a char but still complete it
396 458 return CodeMirror.Pass;
397 459 }
398 460 };
399 461
400 462 var extraKeys = {
401 463 "'@'": CodeMirrorCompleteAfter,
402 464 Tab: function(cm) {
403 465 // space indent instead of TABS
404 466 var spaces = new Array(cm.getOption("indentUnit") + 1).join(" ");
405 467 cm.replaceSelection(spaces);
406 468 }
407 469 };
408 470 // submit form on Meta-Enter
409 471 if (OSType === "mac") {
410 472 extraKeys["Cmd-Enter"] = submitForm;
411 473 }
412 474 else {
413 475 extraKeys["Ctrl-Enter"] = submitForm;
414 476 }
415 477
416 478 if (triggerActions) {
417 479 // register triggerActions for this instance
418 480 extraKeys["'/'"] = completeActions(triggerActions);
419 481 }
420 482
421 483 var cm = CodeMirror.fromTextArea($(textAreaId).get(0), {
422 484 lineNumbers: false,
423 485 indentUnit: 4,
424 486 viewportMargin: 30,
425 487 // this is a trick to trigger some logic behind codemirror placeholder
426 488 // it influences styling and behaviour.
427 489 placeholder: " ",
428 490 extraKeys: extraKeys,
429 491 lineWrapping: true
430 492 });
431 493
432 494 cm.setSize(null, initialHeight);
433 495 cm.setOption("mode", DEFAULT_RENDERER);
434 496 CodeMirror.autoLoadMode(cm, DEFAULT_RENDERER); // load rst or markdown mode
435 497 cmLog.debug('Loading codemirror mode', DEFAULT_RENDERER);
436 498 // start listening on changes to make auto-expanded editor
437 499 cm.on("change", function(self) {
438 500 var height = initialHeight;
439 501 var lines = self.lineCount();
440 502 if ( lines > 6 && lines < 20) {
441 503 height = "auto";
442 504 }
443 505 else if (lines >= 20){
444 506 zheight = 20*15;
445 507 }
446 508 self.setSize(null, height);
447 509 });
448 510
449 511 var actionHint = function(editor, options) {
450 512
451 513 var cur = editor.getCursor();
452 514 var curLine = editor.getLine(cur.line).slice(0, cur.ch);
453 515
454 516 // match only on /+1 character minimum
455 517 var tokenMatch = new RegExp('(^/\|/\)([a-zA-Z]*)$').exec(curLine);
456 518
457 519 var tokenStr = '';
458 520 if (tokenMatch !== null && tokenMatch.length > 0){
459 521 tokenStr = tokenMatch[2].strip();
460 522 }
461 523
462 524 var context = {
463 525 start: (cur.ch - tokenStr.length) - 1,
464 526 end: cur.ch,
465 527 string: tokenStr,
466 528 type: null
467 529 };
468 530
469 531 return {
470 532 list: filterActions(options.registeredActions, context),
471 533 from: CodeMirror.Pos(cur.line, context.start),
472 534 to: CodeMirror.Pos(cur.line, context.end)
473 535 };
474 536
475 537 };
476 538 CodeMirror.registerHelper("hint", "mentions", CodeMirrorMentionHint);
477 539 CodeMirror.registerHelper("hint", "actions", actionHint);
478 540 return cm;
479 541 };
480 542
481 543 var setCodeMirrorMode = function(codeMirrorInstance, mode) {
482 544 CodeMirror.autoLoadMode(codeMirrorInstance, mode);
483 545 codeMirrorInstance.setOption("mode", mode);
484 546 };
485 547
486 548 var setCodeMirrorLineWrap = function(codeMirrorInstance, line_wrap) {
487 549 codeMirrorInstance.setOption("lineWrapping", line_wrap);
488 550 };
489 551
490 552 var setCodeMirrorModeFromSelect = function(
491 553 targetSelect, targetFileInput, codeMirrorInstance, callback){
492 554
493 555 $(targetSelect).on('change', function(e) {
494 556 cmLog.debug('codemirror select2 mode change event !');
495 557 var selected = e.currentTarget;
496 558 var node = selected.options[selected.selectedIndex];
497 559 var mimetype = node.value;
498 560 cmLog.debug('picked mimetype', mimetype);
499 561 var new_mode = $(node).attr('mode');
500 562 setCodeMirrorMode(codeMirrorInstance, new_mode);
501 563 cmLog.debug('set new mode', new_mode);
502 564
503 565 //propose filename from picked mode
504 566 cmLog.debug('setting mimetype', mimetype);
505 567 var proposed_ext = getExtFromMimeType(mimetype);
506 568 cmLog.debug('file input', $(targetFileInput).val());
507 569 var file_data = getFilenameAndExt($(targetFileInput).val());
508 570 var filename = file_data.filename || 'filename1';
509 571 $(targetFileInput).val(filename + proposed_ext);
510 572 cmLog.debug('proposed file', filename + proposed_ext);
511 573
512 574
513 575 if (typeof(callback) === 'function') {
514 576 try {
515 577 cmLog.debug('running callback', callback);
516 578 callback(filename, mimetype, new_mode);
517 579 } catch (err) {
518 580 console.log('failed to run callback', callback, err);
519 581 }
520 582 }
521 583 cmLog.debug('finish iteration...');
522 584 });
523 585 };
524 586
525 587 var setCodeMirrorModeFromInput = function(
526 588 targetSelect, targetFileInput, codeMirrorInstance, callback) {
527 589
528 590 // on type the new filename set mode
529 591 $(targetFileInput).on('keyup', function(e) {
530 592 var file_data = getFilenameAndExt(this.value);
531 593 if (file_data.ext === null) {
532 594 return;
533 595 }
534 596
535 597 var mimetypes = getMimeTypeFromExt(file_data.ext, true);
536 598 cmLog.debug('mimetype from file', file_data, mimetypes);
537 599 var detected_mode;
538 600 var detected_option;
539 601 for (var i in mimetypes) {
540 602 var mt = mimetypes[i];
541 603 if (!detected_mode) {
542 604 detected_mode = detectCodeMirrorMode(this.value, mt);
543 605 }
544 606
545 607 if (!detected_option) {
546 608 cmLog.debug('#mimetype option[value="{0}"]'.format(mt));
547 609 if ($(targetSelect).find('option[value="{0}"]'.format(mt)).length) {
548 610 detected_option = mt;
549 611 }
550 612 }
551 613 }
552 614
553 615 cmLog.debug('detected mode', detected_mode);
554 616 cmLog.debug('detected option', detected_option);
555 617 if (detected_mode && detected_option){
556 618
557 619 $(targetSelect).select2("val", detected_option);
558 620 setCodeMirrorMode(codeMirrorInstance, detected_mode);
559 621
560 622 if(typeof(callback) === 'function'){
561 623 try{
562 624 cmLog.debug('running callback', callback);
563 625 var filename = file_data.filename + "." + file_data.ext;
564 626 callback(filename, detected_option, detected_mode);
565 627 }catch (err){
566 628 console.log('failed to run callback', callback, err);
567 629 }
568 630 }
569 631 }
570 632
571 633 });
572 634 };
573 635
574 636 var fillCodeMirrorOptions = function(targetSelect) {
575 637 //inject new modes, based on codeMirrors modeInfo object
576 638 var modes_select = $(targetSelect);
577 639 for (var i = 0; i < CodeMirror.modeInfo.length; i++) {
578 640 var m = CodeMirror.modeInfo[i];
579 641 var opt = new Option(m.name, m.mime);
580 642 $(opt).attr('mode', m.mode);
581 643 modes_select.append(opt);
582 644 }
583 645 };
584 646
585 647 var CodeMirrorPreviewEnable = function(edit_mode) {
586 648 // in case it a preview enabled mode enable the button
587 649 if (['markdown', 'rst', 'gfm'].indexOf(edit_mode) !== -1) {
588 650 $('#render_preview').removeClass('hidden');
589 651 }
590 652 else {
591 653 if (!$('#render_preview').hasClass('hidden')) {
592 654 $('#render_preview').addClass('hidden');
593 655 }
594 656 }
595 657 };
658
659
660 /* markup form */
661 (function(mod) {
662
663 if (typeof exports == "object" && typeof module == "object") {
664 // CommonJS
665 module.exports = mod();
666 }
667 else {
668 // Plain browser env
669 (this || window).MarkupForm = mod();
670 }
671
672 })(function() {
673 "use strict";
674
675 function MarkupForm(textareaId) {
676 if (!(this instanceof MarkupForm)) {
677 return new MarkupForm(textareaId);
678 }
679
680 // bind the element instance to our Form
681 $('#' + textareaId).get(0).MarkupForm = this;
682
683 this.withSelectorId = function(selector) {
684 var selectorId = textareaId;
685 return selector + '_' + selectorId;
686 };
687
688 this.previewButton = this.withSelectorId('#preview-btn');
689 this.previewContainer = this.withSelectorId('#preview-container');
690
691 this.previewBoxSelector = this.withSelectorId('#preview-box');
692
693 this.editButton = this.withSelectorId('#edit-btn');
694 this.editContainer = this.withSelectorId('#edit-container');
695
696 this.cmBox = textareaId;
697 this.cm = initMarkupCodeMirror('#' + textareaId);
698
699 this.previewUrl = pyroutes.url('markup_preview');
700
701 // FUNCTIONS and helpers
702 var self = this;
703
704 this.getCmInstance = function(){
705 return this.cm
706 };
707
708 this.setPlaceholder = function(placeholder) {
709 var cm = this.getCmInstance();
710 if (cm){
711 cm.setOption('placeholder', placeholder);
712 }
713 };
714
715 this.initStatusChangeSelector = function(){
716 var formatChangeStatus = function(state, escapeMarkup) {
717 var originalOption = state.element;
718 return '<div class="flag_status ' + $(originalOption).data('status') + ' pull-left"></div>' +
719 '<span>' + escapeMarkup(state.text) + '</span>';
720 };
721 var formatResult = function(result, container, query, escapeMarkup) {
722 return formatChangeStatus(result, escapeMarkup);
723 };
724
725 var formatSelection = function(data, container, escapeMarkup) {
726 return formatChangeStatus(data, escapeMarkup);
727 };
728
729 $(this.submitForm).find(this.statusChange).select2({
730 placeholder: _gettext('Status Review'),
731 formatResult: formatResult,
732 formatSelection: formatSelection,
733 containerCssClass: "drop-menu status_box_menu",
734 dropdownCssClass: "drop-menu-dropdown",
735 dropdownAutoWidth: true,
736 minimumResultsForSearch: -1
737 });
738 $(this.submitForm).find(this.statusChange).on('change', function() {
739 var status = self.getCommentStatus();
740
741 if (status && !self.isInline()) {
742 $(self.submitButton).prop('disabled', false);
743 }
744
745 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
746 self.setPlaceholder(placeholderText)
747 })
748 };
749
750 // reset the text area into it's original state
751 this.resetMarkupFormState = function(content) {
752 content = content || '';
753
754 $(this.editContainer).show();
755 $(this.editButton).parent().addClass('active');
756
757 $(this.previewContainer).hide();
758 $(this.previewButton).parent().removeClass('active');
759
760 this.setActionButtonsDisabled(true);
761 self.cm.setValue(content);
762 self.cm.setOption("readOnly", false);
763 };
764
765 this.previewSuccessCallback = function(o) {
766 $(self.previewBoxSelector).html(o);
767 $(self.previewBoxSelector).removeClass('unloaded');
768
769 // swap buttons, making preview active
770 $(self.previewButton).parent().addClass('active');
771 $(self.editButton).parent().removeClass('active');
772
773 // unlock buttons
774 self.setActionButtonsDisabled(false);
775 };
776
777 this.setActionButtonsDisabled = function(state) {
778 $(this.editButton).prop('disabled', state);
779 $(this.previewButton).prop('disabled', state);
780 };
781
782 // lock preview/edit/submit buttons on load, but exclude cancel button
783 var excludeCancelBtn = true;
784 this.setActionButtonsDisabled(true);
785
786 // anonymous users don't have access to initialized CM instance
787 if (this.cm !== undefined){
788 this.cm.on('change', function(cMirror) {
789 if (cMirror.getValue() === "") {
790 self.setActionButtonsDisabled(true)
791 } else {
792 self.setActionButtonsDisabled(false)
793 }
794 });
795 }
796
797 $(this.editButton).on('click', function(e) {
798 e.preventDefault();
799
800 $(self.previewButton).parent().removeClass('active');
801 $(self.previewContainer).hide();
802
803 $(self.editButton).parent().addClass('active');
804 $(self.editContainer).show();
805
806 });
807
808 $(this.previewButton).on('click', function(e) {
809 e.preventDefault();
810 var text = self.cm.getValue();
811
812 if (text === "") {
813 return;
814 }
815
816 var postData = {
817 'text': text,
818 'renderer': templateContext.visual.default_renderer,
819 'csrf_token': CSRF_TOKEN
820 };
821
822 // lock ALL buttons on preview
823 self.setActionButtonsDisabled(true);
824
825 $(self.previewBoxSelector).addClass('unloaded');
826 $(self.previewBoxSelector).html(_gettext('Loading ...'));
827
828 $(self.editContainer).hide();
829 $(self.previewContainer).show();
830
831 // by default we reset state of comment preserving the text
832 var previewFailCallback = function(data){
833 alert(
834 "Error while submitting preview.\n" +
835 "Error code {0} ({1}).".format(data.status, data.statusText)
836 );
837 self.resetMarkupFormState(text)
838 };
839 _submitAjaxPOST(
840 self.previewUrl, postData, self.previewSuccessCallback,
841 previewFailCallback);
842
843 $(self.previewButton).parent().addClass('active');
844 $(self.editButton).parent().removeClass('active');
845 });
846
847 }
848
849 return MarkupForm;
850 });
@@ -1,811 +1,829 b''
1 1 // # Copyright (C) 2010-2018 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19 var firefoxAnchorFix = function() {
20 20 // hack to make anchor links behave properly on firefox, in our inline
21 21 // comments generation when comments are injected firefox is misbehaving
22 22 // when jumping to anchor links
23 23 if (location.href.indexOf('#') > -1) {
24 24 location.href += '';
25 25 }
26 26 };
27 27
28 28 var linkifyComments = function(comments) {
29 29 var firstCommentId = null;
30 30 if (comments) {
31 31 firstCommentId = $(comments[0]).data('comment-id');
32 32 }
33 33
34 34 if (firstCommentId){
35 35 $('#inline-comments-counter').attr('href', '#comment-' + firstCommentId);
36 36 }
37 37 };
38 38
39 39 var bindToggleButtons = function() {
40 40 $('.comment-toggle').on('click', function() {
41 41 $(this).parent().nextUntil('tr.line').toggle('inline-comments');
42 42 });
43 43 };
44 44
45
46
47 var _submitAjaxPOST = function(url, postData, successHandler, failHandler) {
48 failHandler = failHandler || function() {};
49 postData = toQueryString(postData);
50 var request = $.ajax({
51 url: url,
52 type: 'POST',
53 data: postData,
54 headers: {'X-PARTIAL-XHR': true}
55 })
56 .done(function (data) {
57 successHandler(data);
58 })
59 .fail(function (data, textStatus, errorThrown) {
60 failHandler(data, textStatus, errorThrown)
61 });
62 return request;
63 };
64
65
66
67
45 68 /* Comment form for main and inline comments */
46 69 (function(mod) {
47 70
48 71 if (typeof exports == "object" && typeof module == "object") {
49 72 // CommonJS
50 73 module.exports = mod();
51 74 }
52 75 else {
53 76 // Plain browser env
54 77 (this || window).CommentForm = mod();
55 78 }
56 79
57 80 })(function() {
58 81 "use strict";
59 82
60 83 function CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId) {
61 84 if (!(this instanceof CommentForm)) {
62 85 return new CommentForm(formElement, commitId, pullRequestId, lineNo, initAutocompleteActions, resolvesCommentId);
63 86 }
64 87
65 88 // bind the element instance to our Form
66 89 $(formElement).get(0).CommentForm = this;
67 90
68 91 this.withLineNo = function(selector) {
69 92 var lineNo = this.lineNo;
70 93 if (lineNo === undefined) {
71 94 return selector
72 95 } else {
73 96 return selector + '_' + lineNo;
74 97 }
75 98 };
76 99
77 100 this.commitId = commitId;
78 101 this.pullRequestId = pullRequestId;
79 102 this.lineNo = lineNo;
80 103 this.initAutocompleteActions = initAutocompleteActions;
81 104
82 105 this.previewButton = this.withLineNo('#preview-btn');
83 106 this.previewContainer = this.withLineNo('#preview-container');
84 107
85 108 this.previewBoxSelector = this.withLineNo('#preview-box');
86 109
87 110 this.editButton = this.withLineNo('#edit-btn');
88 111 this.editContainer = this.withLineNo('#edit-container');
89 112 this.cancelButton = this.withLineNo('#cancel-btn');
90 113 this.commentType = this.withLineNo('#comment_type');
91 114
92 115 this.resolvesId = null;
93 116 this.resolvesActionId = null;
94 117
95 118 this.closesPr = '#close_pull_request';
96 119
97 120 this.cmBox = this.withLineNo('#text');
98 121 this.cm = initCommentBoxCodeMirror(this, this.cmBox, this.initAutocompleteActions);
99 122
100 123 this.statusChange = this.withLineNo('#change_status');
101 124
102 125 this.submitForm = formElement;
103 126 this.submitButton = $(this.submitForm).find('input[type="submit"]');
104 127 this.submitButtonText = this.submitButton.val();
105 128
106 129 this.previewUrl = pyroutes.url('repo_commit_comment_preview',
107 130 {'repo_name': templateContext.repo_name,
108 131 'commit_id': templateContext.commit_data.commit_id});
109 132
110 133 if (resolvesCommentId){
111 134 this.resolvesId = '#resolve_comment_{0}'.format(resolvesCommentId);
112 135 this.resolvesActionId = '#resolve_comment_action_{0}'.format(resolvesCommentId);
113 136 $(this.commentType).prop('disabled', true);
114 137 $(this.commentType).addClass('disabled');
115 138
116 139 // disable select
117 140 setTimeout(function() {
118 141 $(self.statusChange).select2('readonly', true);
119 142 }, 10);
120 143
121 144 var resolvedInfo = (
122 145 '<li class="resolve-action">' +
123 146 '<input type="hidden" id="resolve_comment_{0}" name="resolve_comment_{0}" value="{0}">' +
124 147 '<button id="resolve_comment_action_{0}" class="resolve-text btn btn-sm" onclick="return Rhodecode.comments.submitResolution({0})">{1} #{0}</button>' +
125 148 '</li>'
126 149 ).format(resolvesCommentId, _gettext('resolve comment'));
127 150 $(resolvedInfo).insertAfter($(this.commentType).parent());
128 151 }
129 152
130 153 // based on commitId, or pullRequestId decide where do we submit
131 154 // out data
132 155 if (this.commitId){
133 156 this.submitUrl = pyroutes.url('repo_commit_comment_create',
134 157 {'repo_name': templateContext.repo_name,
135 158 'commit_id': this.commitId});
136 159 this.selfUrl = pyroutes.url('repo_commit',
137 160 {'repo_name': templateContext.repo_name,
138 161 'commit_id': this.commitId});
139 162
140 163 } else if (this.pullRequestId) {
141 164 this.submitUrl = pyroutes.url('pullrequest_comment_create',
142 165 {'repo_name': templateContext.repo_name,
143 166 'pull_request_id': this.pullRequestId});
144 167 this.selfUrl = pyroutes.url('pullrequest_show',
145 168 {'repo_name': templateContext.repo_name,
146 169 'pull_request_id': this.pullRequestId});
147 170
148 171 } else {
149 172 throw new Error(
150 173 'CommentForm requires pullRequestId, or commitId to be specified.')
151 174 }
152 175
153 176 // FUNCTIONS and helpers
154 177 var self = this;
155 178
156 179 this.isInline = function(){
157 180 return this.lineNo && this.lineNo != 'general';
158 181 };
159 182
160 183 this.getCmInstance = function(){
161 184 return this.cm
162 185 };
163 186
164 187 this.setPlaceholder = function(placeholder) {
165 188 var cm = this.getCmInstance();
166 189 if (cm){
167 190 cm.setOption('placeholder', placeholder);
168 191 }
169 192 };
170 193
171 194 this.getCommentStatus = function() {
172 195 return $(this.submitForm).find(this.statusChange).val();
173 196 };
174 197 this.getCommentType = function() {
175 198 return $(this.submitForm).find(this.commentType).val();
176 199 };
177 200
178 201 this.getResolvesId = function() {
179 202 return $(this.submitForm).find(this.resolvesId).val() || null;
180 203 };
181 204
182 205 this.getClosePr = function() {
183 206 return $(this.submitForm).find(this.closesPr).val() || null;
184 207 };
185 208
186 209 this.markCommentResolved = function(resolvedCommentId){
187 210 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolved').show();
188 211 $('#comment-label-{0}'.format(resolvedCommentId)).find('.resolve').hide();
189 212 };
190 213
191 214 this.isAllowedToSubmit = function() {
192 215 return !$(this.submitButton).prop('disabled');
193 216 };
194 217
195 218 this.initStatusChangeSelector = function(){
196 219 var formatChangeStatus = function(state, escapeMarkup) {
197 220 var originalOption = state.element;
198 221 return '<div class="flag_status ' + $(originalOption).data('status') + ' pull-left"></div>' +
199 222 '<span>' + escapeMarkup(state.text) + '</span>';
200 223 };
201 224 var formatResult = function(result, container, query, escapeMarkup) {
202 225 return formatChangeStatus(result, escapeMarkup);
203 226 };
204 227
205 228 var formatSelection = function(data, container, escapeMarkup) {
206 229 return formatChangeStatus(data, escapeMarkup);
207 230 };
208 231
209 232 $(this.submitForm).find(this.statusChange).select2({
210 233 placeholder: _gettext('Status Review'),
211 234 formatResult: formatResult,
212 235 formatSelection: formatSelection,
213 236 containerCssClass: "drop-menu status_box_menu",
214 237 dropdownCssClass: "drop-menu-dropdown",
215 238 dropdownAutoWidth: true,
216 239 minimumResultsForSearch: -1
217 240 });
218 241 $(this.submitForm).find(this.statusChange).on('change', function() {
219 242 var status = self.getCommentStatus();
220 243
221 244 if (status && !self.isInline()) {
222 245 $(self.submitButton).prop('disabled', false);
223 246 }
224 247
225 248 var placeholderText = _gettext('Comment text will be set automatically based on currently selected status ({0}) ...').format(status);
226 249 self.setPlaceholder(placeholderText)
227 250 })
228 251 };
229 252
230 253 // reset the comment form into it's original state
231 254 this.resetCommentFormState = function(content) {
232 255 content = content || '';
233 256
234 257 $(this.editContainer).show();
235 258 $(this.editButton).parent().addClass('active');
236 259
237 260 $(this.previewContainer).hide();
238 261 $(this.previewButton).parent().removeClass('active');
239 262
240 263 this.setActionButtonsDisabled(true);
241 264 self.cm.setValue(content);
242 265 self.cm.setOption("readOnly", false);
243 266
244 267 if (this.resolvesId) {
245 268 // destroy the resolve action
246 269 $(this.resolvesId).parent().remove();
247 270 }
248 271 // reset closingPR flag
249 272 $('.close-pr-input').remove();
250 273
251 274 $(this.statusChange).select2('readonly', false);
252 275 };
253 276
254 277 this.globalSubmitSuccessCallback = function(){
255 278 // default behaviour is to call GLOBAL hook, if it's registered.
256 279 if (window.commentFormGlobalSubmitSuccessCallback !== undefined){
257 280 commentFormGlobalSubmitSuccessCallback()
258 281 }
259 282 };
260 283
261 284 this.submitAjaxPOST = function(url, postData, successHandler, failHandler) {
262 failHandler = failHandler || function() {};
263 var postData = toQueryString(postData);
264 var request = $.ajax({
265 url: url,
266 type: 'POST',
267 data: postData,
268 headers: {'X-PARTIAL-XHR': true}
269 })
270 .done(function(data) {
271 successHandler(data);
272 })
273 .fail(function(data, textStatus, errorThrown){
274 alert(
275 "Error while submitting comment.\n" +
276 "Error code {0} ({1}).".format(data.status, data.statusText));
277 failHandler()
278 });
279 return request;
285 return _submitAjaxPOST(url, postData, successHandler, failHandler);
280 286 };
281 287
282 288 // overwrite a submitHandler, we need to do it for inline comments
283 289 this.setHandleFormSubmit = function(callback) {
284 290 this.handleFormSubmit = callback;
285 291 };
286 292
287 293 // overwrite a submitSuccessHandler
288 294 this.setGlobalSubmitSuccessCallback = function(callback) {
289 295 this.globalSubmitSuccessCallback = callback;
290 296 };
291 297
292 298 // default handler for for submit for main comments
293 299 this.handleFormSubmit = function() {
294 300 var text = self.cm.getValue();
295 301 var status = self.getCommentStatus();
296 302 var commentType = self.getCommentType();
297 303 var resolvesCommentId = self.getResolvesId();
298 304 var closePullRequest = self.getClosePr();
299 305
300 306 if (text === "" && !status) {
301 307 return;
302 308 }
303 309
304 310 var excludeCancelBtn = false;
305 311 var submitEvent = true;
306 312 self.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
307 313 self.cm.setOption("readOnly", true);
308 314
309 315 var postData = {
310 316 'text': text,
311 317 'changeset_status': status,
312 318 'comment_type': commentType,
313 319 'csrf_token': CSRF_TOKEN
314 320 };
315 321
316 322 if (resolvesCommentId) {
317 323 postData['resolves_comment_id'] = resolvesCommentId;
318 324 }
319 325
320 326 if (closePullRequest) {
321 327 postData['close_pull_request'] = true;
322 328 }
323 329
324 330 var submitSuccessCallback = function(o) {
325 331 // reload page if we change status for single commit.
326 332 if (status && self.commitId) {
327 333 location.reload(true);
328 334 } else {
329 335 $('#injected_page_comments').append(o.rendered_text);
330 336 self.resetCommentFormState();
331 337 timeagoActivate();
332 338
333 339 // mark visually which comment was resolved
334 340 if (resolvesCommentId) {
335 341 self.markCommentResolved(resolvesCommentId);
336 342 }
337 343 }
338 344
339 345 // run global callback on submit
340 346 self.globalSubmitSuccessCallback();
341 347
342 348 };
343 var submitFailCallback = function(){
349 var submitFailCallback = function(data) {
350 alert(
351 "Error while submitting comment.\n" +
352 "Error code {0} ({1}).".format(data.status, data.statusText)
353 );
344 354 self.resetCommentFormState(text);
345 355 };
346 356 self.submitAjaxPOST(
347 357 self.submitUrl, postData, submitSuccessCallback, submitFailCallback);
348 358 };
349 359
350 360 this.previewSuccessCallback = function(o) {
351 361 $(self.previewBoxSelector).html(o);
352 362 $(self.previewBoxSelector).removeClass('unloaded');
353 363
354 364 // swap buttons, making preview active
355 365 $(self.previewButton).parent().addClass('active');
356 366 $(self.editButton).parent().removeClass('active');
357 367
358 368 // unlock buttons
359 369 self.setActionButtonsDisabled(false);
360 370 };
361 371
362 372 this.setActionButtonsDisabled = function(state, excludeCancelBtn, submitEvent) {
363 373 excludeCancelBtn = excludeCancelBtn || false;
364 374 submitEvent = submitEvent || false;
365 375
366 376 $(this.editButton).prop('disabled', state);
367 377 $(this.previewButton).prop('disabled', state);
368 378
369 379 if (!excludeCancelBtn) {
370 380 $(this.cancelButton).prop('disabled', state);
371 381 }
372 382
373 383 var submitState = state;
374 384 if (!submitEvent && this.getCommentStatus() && !self.isInline()) {
375 385 // if the value of commit review status is set, we allow
376 386 // submit button, but only on Main form, isInline means inline
377 387 submitState = false
378 388 }
379 389
380 390 $(this.submitButton).prop('disabled', submitState);
381 391 if (submitEvent) {
382 392 $(this.submitButton).val(_gettext('Submitting...'));
383 393 } else {
384 394 $(this.submitButton).val(this.submitButtonText);
385 395 }
386 396
387 397 };
388 398
389 399 // lock preview/edit/submit buttons on load, but exclude cancel button
390 400 var excludeCancelBtn = true;
391 401 this.setActionButtonsDisabled(true, excludeCancelBtn);
392 402
393 403 // anonymous users don't have access to initialized CM instance
394 404 if (this.cm !== undefined){
395 405 this.cm.on('change', function(cMirror) {
396 406 if (cMirror.getValue() === "") {
397 407 self.setActionButtonsDisabled(true, excludeCancelBtn)
398 408 } else {
399 409 self.setActionButtonsDisabled(false, excludeCancelBtn)
400 410 }
401 411 });
402 412 }
403 413
404 414 $(this.editButton).on('click', function(e) {
405 415 e.preventDefault();
406 416
407 417 $(self.previewButton).parent().removeClass('active');
408 418 $(self.previewContainer).hide();
409 419
410 420 $(self.editButton).parent().addClass('active');
411 421 $(self.editContainer).show();
412 422
413 423 });
414 424
415 425 $(this.previewButton).on('click', function(e) {
416 426 e.preventDefault();
417 427 var text = self.cm.getValue();
418 428
419 429 if (text === "") {
420 430 return;
421 431 }
422 432
423 433 var postData = {
424 434 'text': text,
425 435 'renderer': templateContext.visual.default_renderer,
426 436 'csrf_token': CSRF_TOKEN
427 437 };
428 438
429 439 // lock ALL buttons on preview
430 440 self.setActionButtonsDisabled(true);
431 441
432 442 $(self.previewBoxSelector).addClass('unloaded');
433 443 $(self.previewBoxSelector).html(_gettext('Loading ...'));
434 444
435 445 $(self.editContainer).hide();
436 446 $(self.previewContainer).show();
437 447
438 448 // by default we reset state of comment preserving the text
439 var previewFailCallback = function(){
449 var previewFailCallback = function(data){
450 alert(
451 "Error while preview of comment.\n" +
452 "Error code {0} ({1}).".format(data.status, data.statusText)
453 );
440 454 self.resetCommentFormState(text)
441 455 };
442 456 self.submitAjaxPOST(
443 457 self.previewUrl, postData, self.previewSuccessCallback,
444 458 previewFailCallback);
445 459
446 460 $(self.previewButton).parent().addClass('active');
447 461 $(self.editButton).parent().removeClass('active');
448 462 });
449 463
450 464 $(this.submitForm).submit(function(e) {
451 465 e.preventDefault();
452 466 var allowedToSubmit = self.isAllowedToSubmit();
453 467 if (!allowedToSubmit){
454 468 return false;
455 469 }
456 470 self.handleFormSubmit();
457 471 });
458 472
459 473 }
460 474
461 475 return CommentForm;
462 476 });
463 477
464 478 /* comments controller */
465 479 var CommentsController = function() {
466 480 var mainComment = '#text';
467 481 var self = this;
468 482
469 483 this.cancelComment = function(node) {
470 484 var $node = $(node);
471 485 var $td = $node.closest('td');
472 486 $node.closest('.comment-inline-form').remove();
473 487 return false;
474 488 };
475 489
476 490 this.getLineNumber = function(node) {
477 491 var $node = $(node);
478 492 var lineNo = $node.closest('td').attr('data-line-no');
479 493 if (lineNo === undefined && $node.data('commentInline')){
480 494 lineNo = $node.data('commentLineNo')
481 495 }
482 496
483 497 return lineNo
484 498 };
485 499
486 500 this.scrollToComment = function(node, offset, outdated) {
487 501 if (offset === undefined) {
488 502 offset = 0;
489 503 }
490 504 var outdated = outdated || false;
491 505 var klass = outdated ? 'div.comment-outdated' : 'div.comment-current';
492 506
493 507 if (!node) {
494 508 node = $('.comment-selected');
495 509 if (!node.length) {
496 510 node = $('comment-current')
497 511 }
498 512 }
499 513 $wrapper = $(node).closest('div.comment');
500 514 $comment = $(node).closest(klass);
501 515 $comments = $(klass);
502 516
503 517 // show hidden comment when referenced.
504 518 if (!$wrapper.is(':visible')){
505 519 $wrapper.show();
506 520 }
507 521
508 522 $('.comment-selected').removeClass('comment-selected');
509 523
510 524 var nextIdx = $(klass).index($comment) + offset;
511 525 if (nextIdx >= $comments.length) {
512 526 nextIdx = 0;
513 527 }
514 528 var $next = $(klass).eq(nextIdx);
515 529
516 530 var $cb = $next.closest('.cb');
517 531 $cb.removeClass('cb-collapsed');
518 532
519 533 var $filediffCollapseState = $cb.closest('.filediff').prev();
520 534 $filediffCollapseState.prop('checked', false);
521 535 $next.addClass('comment-selected');
522 536 scrollToElement($next);
523 537 return false;
524 538 };
525 539
526 540 this.nextComment = function(node) {
527 541 return self.scrollToComment(node, 1);
528 542 };
529 543
530 544 this.prevComment = function(node) {
531 545 return self.scrollToComment(node, -1);
532 546 };
533 547
534 548 this.nextOutdatedComment = function(node) {
535 549 return self.scrollToComment(node, 1, true);
536 550 };
537 551
538 552 this.prevOutdatedComment = function(node) {
539 553 return self.scrollToComment(node, -1, true);
540 554 };
541 555
542 556 this.deleteComment = function(node) {
543 557 if (!confirm(_gettext('Delete this comment?'))) {
544 558 return false;
545 559 }
546 560 var $node = $(node);
547 561 var $td = $node.closest('td');
548 562 var $comment = $node.closest('.comment');
549 563 var comment_id = $comment.attr('data-comment-id');
550 564 var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__', comment_id);
551 565 var postData = {
552 566 'csrf_token': CSRF_TOKEN
553 567 };
554 568
555 569 $comment.addClass('comment-deleting');
556 570 $comment.hide('fast');
557 571
558 572 var success = function(response) {
559 573 $comment.remove();
560 574 return false;
561 575 };
562 576 var failure = function(data, textStatus, xhr) {
563 577 alert("error processing request: " + textStatus);
564 578 $comment.show('fast');
565 579 $comment.removeClass('comment-deleting');
566 580 return false;
567 581 };
568 582 ajaxPOST(url, postData, success, failure);
569 583 };
570 584
571 585 this.toggleWideMode = function (node) {
572 586 if ($('#content').hasClass('wrapper')) {
573 587 $('#content').removeClass("wrapper");
574 588 $('#content').addClass("wide-mode-wrapper");
575 589 $(node).addClass('btn-success');
576 590 } else {
577 591 $('#content').removeClass("wide-mode-wrapper");
578 592 $('#content').addClass("wrapper");
579 593 $(node).removeClass('btn-success');
580 594 }
581 595 return false;
582 596 };
583 597
584 598 this.toggleComments = function(node, show) {
585 599 var $filediff = $(node).closest('.filediff');
586 600 if (show === true) {
587 601 $filediff.removeClass('hide-comments');
588 602 } else if (show === false) {
589 603 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
590 604 $filediff.addClass('hide-comments');
591 605 } else {
592 606 $filediff.find('.hide-line-comments').removeClass('hide-line-comments');
593 607 $filediff.toggleClass('hide-comments');
594 608 }
595 609 return false;
596 610 };
597 611
598 612 this.toggleLineComments = function(node) {
599 613 self.toggleComments(node, true);
600 614 var $node = $(node);
601 615 // mark outdated comments as visible before the toggle;
602 616 $(node.closest('tr')).find('.comment-outdated').show();
603 617 $node.closest('tr').toggleClass('hide-line-comments');
604 618 };
605 619
606 620 this.createCommentForm = function(formElement, lineno, placeholderText, initAutocompleteActions, resolvesCommentId){
607 621 var pullRequestId = templateContext.pull_request_data.pull_request_id;
608 622 var commitId = templateContext.commit_data.commit_id;
609 623
610 624 var commentForm = new CommentForm(
611 625 formElement, commitId, pullRequestId, lineno, initAutocompleteActions, resolvesCommentId);
612 626 var cm = commentForm.getCmInstance();
613 627
614 628 if (resolvesCommentId){
615 629 var placeholderText = _gettext('Leave a comment, or click resolve button to resolve TODO comment #{0}').format(resolvesCommentId);
616 630 }
617 631
618 632 setTimeout(function() {
619 633 // callbacks
620 634 if (cm !== undefined) {
621 635 commentForm.setPlaceholder(placeholderText);
622 636 if (commentForm.isInline()) {
623 637 cm.focus();
624 638 cm.refresh();
625 639 }
626 640 }
627 641 }, 10);
628 642
629 643 // trigger scrolldown to the resolve comment, since it might be away
630 644 // from the clicked
631 645 if (resolvesCommentId){
632 646 var actionNode = $(commentForm.resolvesActionId).offset();
633 647
634 648 setTimeout(function() {
635 649 if (actionNode) {
636 650 $('body, html').animate({scrollTop: actionNode.top}, 10);
637 651 }
638 652 }, 100);
639 653 }
640 654
641 655 return commentForm;
642 656 };
643 657
644 658 this.createGeneralComment = function (lineNo, placeholderText, resolvesCommentId) {
645 659
646 660 var tmpl = $('#cb-comment-general-form-template').html();
647 661 tmpl = tmpl.format(null, 'general');
648 662 var $form = $(tmpl);
649 663
650 664 var $formPlaceholder = $('#cb-comment-general-form-placeholder');
651 665 var curForm = $formPlaceholder.find('form');
652 666 if (curForm){
653 667 curForm.remove();
654 668 }
655 669 $formPlaceholder.append($form);
656 670
657 671 var _form = $($form[0]);
658 672 var autocompleteActions = ['approve', 'reject', 'as_note', 'as_todo'];
659 673 var commentForm = this.createCommentForm(
660 674 _form, lineNo, placeholderText, autocompleteActions, resolvesCommentId);
661 675 commentForm.initStatusChangeSelector();
662 676
663 677 return commentForm;
664 678 };
665 679
666 680 this.createComment = function(node, resolutionComment) {
667 681 var resolvesCommentId = resolutionComment || null;
668 682 var $node = $(node);
669 683 var $td = $node.closest('td');
670 684 var $form = $td.find('.comment-inline-form');
671 685
672 686 if (!$form.length) {
673 687
674 688 var $filediff = $node.closest('.filediff');
675 689 $filediff.removeClass('hide-comments');
676 690 var f_path = $filediff.attr('data-f-path');
677 691 var lineno = self.getLineNumber(node);
678 692 // create a new HTML from template
679 693 var tmpl = $('#cb-comment-inline-form-template').html();
680 694 tmpl = tmpl.format(escapeHtml(f_path), lineno);
681 695 $form = $(tmpl);
682 696
683 697 var $comments = $td.find('.inline-comments');
684 698 if (!$comments.length) {
685 699 $comments = $(
686 700 $('#cb-comments-inline-container-template').html());
687 701 $td.append($comments);
688 702 }
689 703
690 704 $td.find('.cb-comment-add-button').before($form);
691 705
692 706 var placeholderText = _gettext('Leave a comment on line {0}.').format(lineno);
693 707 var _form = $($form[0]).find('form');
694 708 var autocompleteActions = ['as_note', 'as_todo'];
695 709 var commentForm = this.createCommentForm(
696 710 _form, lineno, placeholderText, autocompleteActions, resolvesCommentId);
697 711
698 712 $.Topic('/ui/plugins/code/comment_form_built').prepareOrPublish({
699 713 form: _form,
700 714 parent: $td[0],
701 715 lineno: lineno,
702 716 f_path: f_path}
703 717 );
704 718
705 719 // set a CUSTOM submit handler for inline comments.
706 720 commentForm.setHandleFormSubmit(function(o) {
707 721 var text = commentForm.cm.getValue();
708 722 var commentType = commentForm.getCommentType();
709 723 var resolvesCommentId = commentForm.getResolvesId();
710 724
711 725 if (text === "") {
712 726 return;
713 727 }
714 728
715 729 if (lineno === undefined) {
716 730 alert('missing line !');
717 731 return;
718 732 }
719 733 if (f_path === undefined) {
720 734 alert('missing file path !');
721 735 return;
722 736 }
723 737
724 738 var excludeCancelBtn = false;
725 739 var submitEvent = true;
726 740 commentForm.setActionButtonsDisabled(true, excludeCancelBtn, submitEvent);
727 741 commentForm.cm.setOption("readOnly", true);
728 742 var postData = {
729 743 'text': text,
730 744 'f_path': f_path,
731 745 'line': lineno,
732 746 'comment_type': commentType,
733 747 'csrf_token': CSRF_TOKEN
734 748 };
735 749 if (resolvesCommentId){
736 750 postData['resolves_comment_id'] = resolvesCommentId;
737 751 }
738 752
739 753 var submitSuccessCallback = function(json_data) {
740 754 $form.remove();
741 755 try {
742 756 var html = json_data.rendered_text;
743 757 var lineno = json_data.line_no;
744 758 var target_id = json_data.target_id;
745 759
746 760 $comments.find('.cb-comment-add-button').before(html);
747 761
748 762 //mark visually which comment was resolved
749 763 if (resolvesCommentId) {
750 764 commentForm.markCommentResolved(resolvesCommentId);
751 765 }
752 766
753 767 // run global callback on submit
754 768 commentForm.globalSubmitSuccessCallback();
755 769
756 770 } catch (e) {
757 771 console.error(e);
758 772 }
759 773
760 774 // re trigger the linkification of next/prev navigation
761 775 linkifyComments($('.inline-comment-injected'));
762 776 timeagoActivate();
763 777 commentForm.setActionButtonsDisabled(false);
764 778
765 779 };
766 var submitFailCallback = function(){
780 var submitFailCallback = function(data){
781 alert(
782 "Error while submitting comment.\n" +
783 "Error code {0} ({1}).".format(data.status, data.statusText)
784 );
767 785 commentForm.resetCommentFormState(text)
768 786 };
769 787 commentForm.submitAjaxPOST(
770 788 commentForm.submitUrl, postData, submitSuccessCallback, submitFailCallback);
771 789 });
772 790 }
773 791
774 792 $form.addClass('comment-inline-form-open');
775 793 };
776 794
777 795 this.createResolutionComment = function(commentId){
778 796 // hide the trigger text
779 797 $('#resolve-comment-{0}'.format(commentId)).hide();
780 798
781 799 var comment = $('#comment-'+commentId);
782 800 var commentData = comment.data();
783 801 if (commentData.commentInline) {
784 802 this.createComment(comment, commentId)
785 803 } else {
786 804 Rhodecode.comments.createGeneralComment('general', "$placeholder", commentId)
787 805 }
788 806
789 807 return false;
790 808 };
791 809
792 810 this.submitResolution = function(commentId){
793 811 var form = $('#resolve_comment_{0}'.format(commentId)).closest('form');
794 812 var commentForm = form.get(0).CommentForm;
795 813
796 814 var cm = commentForm.getCmInstance();
797 815 var renderer = templateContext.visual.default_renderer;
798 816 if (renderer == 'rst'){
799 817 var commentUrl = '`#{0} <{1}#comment-{0}>`_'.format(commentId, commentForm.selfUrl);
800 818 } else if (renderer == 'markdown') {
801 819 var commentUrl = '[#{0}]({1}#comment-{0})'.format(commentId, commentForm.selfUrl);
802 820 } else {
803 821 var commentUrl = '{1}#comment-{0}'.format(commentId, commentForm.selfUrl);
804 822 }
805 823
806 824 cm.setValue(_gettext('TODO from comment {0} was fixed.').format(commentUrl));
807 825 form.submit();
808 826 return false;
809 827 };
810 828
811 829 };
@@ -1,593 +1,550 b''
1 1 // # Copyright (C) 2010-2018 RhodeCode GmbH
2 2 // #
3 3 // # This program is free software: you can redistribute it and/or modify
4 4 // # it under the terms of the GNU Affero General Public License, version 3
5 5 // # (only), as published by the Free Software Foundation.
6 6 // #
7 7 // # This program is distributed in the hope that it will be useful,
8 8 // # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 9 // # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 10 // # GNU General Public License for more details.
11 11 // #
12 12 // # You should have received a copy of the GNU Affero General Public License
13 13 // # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 14 // #
15 15 // # This program is dual-licensed. If you wish to learn more about the
16 16 // # RhodeCode Enterprise Edition, including its added features, Support services,
17 17 // # and proprietary license terms, please see https://rhodecode.com/licenses/
18 18
19 19
20 20 var prButtonLockChecks = {
21 21 'compare': false,
22 22 'reviewers': false
23 23 };
24 24
25 25 /**
26 26 * lock button until all checks and loads are made. E.g reviewer calculation
27 27 * should prevent from submitting a PR
28 28 * @param lockEnabled
29 29 * @param msg
30 30 * @param scope
31 31 */
32 32 var prButtonLock = function(lockEnabled, msg, scope) {
33 33 scope = scope || 'all';
34 34 if (scope == 'all'){
35 35 prButtonLockChecks['compare'] = !lockEnabled;
36 36 prButtonLockChecks['reviewers'] = !lockEnabled;
37 37 } else if (scope == 'compare') {
38 38 prButtonLockChecks['compare'] = !lockEnabled;
39 39 } else if (scope == 'reviewers'){
40 40 prButtonLockChecks['reviewers'] = !lockEnabled;
41 41 }
42 42 var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
43 43 if (lockEnabled) {
44 44 $('#pr_submit').attr('disabled', 'disabled');
45 45 }
46 46 else if (checksMeet) {
47 47 $('#pr_submit').removeAttr('disabled');
48 48 }
49 49
50 50 if (msg) {
51 51 $('#pr_open_message').html(msg);
52 52 }
53 53 };
54 54
55 55
56 56 /**
57 57 Generate Title and Description for a PullRequest.
58 58 In case of 1 commits, the title and description is that one commit
59 59 in case of multiple commits, we iterate on them with max N number of commits,
60 60 and build description in a form
61 61 - commitN
62 62 - commitN+1
63 63 ...
64 64
65 65 Title is then constructed from branch names, or other references,
66 66 replacing '-' and '_' into spaces
67 67
68 68 * @param sourceRef
69 69 * @param elements
70 70 * @param limit
71 71 * @returns {*[]}
72 72 */
73 73 var getTitleAndDescription = function(sourceRef, elements, limit) {
74 74 var title = '';
75 75 var desc = '';
76 76
77 77 $.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
78 78 var rawMessage = $(value).find('td.td-description .message').data('messageRaw');
79 79 desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
80 80 });
81 81 // only 1 commit, use commit message as title
82 82 if (elements.length === 1) {
83 83 title = $(elements[0]).find('td.td-description .message').data('messageRaw').split('\n')[0];
84 84 }
85 85 else {
86 86 // use reference name
87 87 title = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter();
88 88 }
89 89
90 90 return [title, desc]
91 91 };
92 92
93 93
94 94
95 95 ReviewersController = function () {
96 96 var self = this;
97 97 this.$reviewRulesContainer = $('#review_rules');
98 98 this.$rulesList = this.$reviewRulesContainer.find('.pr-reviewer-rules');
99 99 this.forbidReviewUsers = undefined;
100 100 this.$reviewMembers = $('#review_members');
101 101 this.currentRequest = null;
102 102
103 103 this.defaultForbidReviewUsers = function() {
104 104 return [
105 105 {'username': 'default',
106 106 'user_id': templateContext.default_user.user_id}
107 107 ];
108 108 };
109 109
110 110 this.hideReviewRules = function() {
111 111 self.$reviewRulesContainer.hide();
112 112 };
113 113
114 114 this.showReviewRules = function() {
115 115 self.$reviewRulesContainer.show();
116 116 };
117 117
118 118 this.addRule = function(ruleText) {
119 119 self.showReviewRules();
120 120 return '<div>- {0}</div>'.format(ruleText)
121 121 };
122 122
123 123 this.loadReviewRules = function(data) {
124 124 // reset forbidden Users
125 125 this.forbidReviewUsers = self.defaultForbidReviewUsers();
126 126
127 127 // reset state of review rules
128 128 self.$rulesList.html('');
129 129
130 130 if (!data || data.rules === undefined || $.isEmptyObject(data.rules)) {
131 131 // default rule, case for older repo that don't have any rules stored
132 132 self.$rulesList.append(
133 133 self.addRule(
134 134 _gettext('All reviewers must vote.'))
135 135 );
136 136 return self.forbidReviewUsers
137 137 }
138 138
139 139 if (data.rules.voting !== undefined) {
140 140 if (data.rules.voting < 0) {
141 141 self.$rulesList.append(
142 142 self.addRule(
143 143 _gettext('All individual reviewers must vote.'))
144 144 )
145 145 } else if (data.rules.voting === 1) {
146 146 self.$rulesList.append(
147 147 self.addRule(
148 148 _gettext('At least {0} reviewer must vote.').format(data.rules.voting))
149 149 )
150 150
151 151 } else {
152 152 self.$rulesList.append(
153 153 self.addRule(
154 154 _gettext('At least {0} reviewers must vote.').format(data.rules.voting))
155 155 )
156 156 }
157 157 }
158 158
159 159 if (data.rules.voting_groups !== undefined) {
160 160 $.each(data.rules.voting_groups, function(index, rule_data) {
161 161 self.$rulesList.append(
162 162 self.addRule(rule_data.text)
163 163 )
164 164 });
165 165 }
166 166
167 167 if (data.rules.use_code_authors_for_review) {
168 168 self.$rulesList.append(
169 169 self.addRule(
170 170 _gettext('Reviewers picked from source code changes.'))
171 171 )
172 172 }
173 173 if (data.rules.forbid_adding_reviewers) {
174 174 $('#add_reviewer_input').remove();
175 175 self.$rulesList.append(
176 176 self.addRule(
177 177 _gettext('Adding new reviewers is forbidden.'))
178 178 )
179 179 }
180 180 if (data.rules.forbid_author_to_review) {
181 181 self.forbidReviewUsers.push(data.rules_data.pr_author);
182 182 self.$rulesList.append(
183 183 self.addRule(
184 184 _gettext('Author is not allowed to be a reviewer.'))
185 185 )
186 186 }
187 187 if (data.rules.forbid_commit_author_to_review) {
188 188
189 189 if (data.rules_data.forbidden_users) {
190 190 $.each(data.rules_data.forbidden_users, function(index, member_data) {
191 191 self.forbidReviewUsers.push(member_data)
192 192 });
193 193
194 194 }
195 195
196 196 self.$rulesList.append(
197 197 self.addRule(
198 198 _gettext('Commit Authors are not allowed to be a reviewer.'))
199 199 )
200 200 }
201 201
202 202 return self.forbidReviewUsers
203 203 };
204 204
205 205 this.loadDefaultReviewers = function(sourceRepo, sourceRef, targetRepo, targetRef) {
206 206
207 207 if (self.currentRequest) {
208 208 // make sure we cleanup old running requests before triggering this
209 209 // again
210 210 self.currentRequest.abort();
211 211 }
212 212
213 213 $('.calculate-reviewers').show();
214 214 // reset reviewer members
215 215 self.$reviewMembers.empty();
216 216
217 217 prButtonLock(true, null, 'reviewers');
218 218 $('#user').hide(); // hide user autocomplete before load
219 219
220 220 if (sourceRef.length !== 3 || targetRef.length !== 3) {
221 221 // don't load defaults in case we're missing some refs...
222 222 $('.calculate-reviewers').hide();
223 223 return
224 224 }
225 225
226 226 var url = pyroutes.url('repo_default_reviewers_data',
227 227 {
228 228 'repo_name': templateContext.repo_name,
229 229 'source_repo': sourceRepo,
230 230 'source_ref': sourceRef[2],
231 231 'target_repo': targetRepo,
232 232 'target_ref': targetRef[2]
233 233 });
234 234
235 235 self.currentRequest = $.get(url)
236 236 .done(function(data) {
237 237 self.currentRequest = null;
238 238
239 239 // review rules
240 240 self.loadReviewRules(data);
241 241
242 242 for (var i = 0; i < data.reviewers.length; i++) {
243 243 var reviewer = data.reviewers[i];
244 244 self.addReviewMember(
245 245 reviewer, reviewer.reasons, reviewer.mandatory);
246 246 }
247 247 $('.calculate-reviewers').hide();
248 248 prButtonLock(false, null, 'reviewers');
249 249 $('#user').show(); // show user autocomplete after load
250 250 });
251 251 };
252 252
253 253 // check those, refactor
254 254 this.removeReviewMember = function(reviewer_id, mark_delete) {
255 255 var reviewer = $('#reviewer_{0}'.format(reviewer_id));
256 256
257 257 if(typeof(mark_delete) === undefined){
258 258 mark_delete = false;
259 259 }
260 260
261 261 if(mark_delete === true){
262 262 if (reviewer){
263 263 // now delete the input
264 264 $('#reviewer_{0} input'.format(reviewer_id)).remove();
265 265 // mark as to-delete
266 266 var obj = $('#reviewer_{0}_name'.format(reviewer_id));
267 267 obj.addClass('to-delete');
268 268 obj.css({"text-decoration":"line-through", "opacity": 0.5});
269 269 }
270 270 }
271 271 else{
272 272 $('#reviewer_{0}'.format(reviewer_id)).remove();
273 273 }
274 274 };
275 275 this.reviewMemberEntry = function() {
276 276
277 277 };
278 278 this.addReviewMember = function(reviewer_obj, reasons, mandatory) {
279 279 var members = self.$reviewMembers.get(0);
280 280 var id = reviewer_obj.user_id;
281 281 var username = reviewer_obj.username;
282 282
283 283 var reasons = reasons || [];
284 284 var mandatory = mandatory || false;
285 285
286 286 // register IDS to check if we don't have this ID already in
287 287 var currentIds = [];
288 288 var _els = self.$reviewMembers.find('li').toArray();
289 289 for (el in _els){
290 290 currentIds.push(_els[el].id)
291 291 }
292 292
293 293 var userAllowedReview = function(userId) {
294 294 var allowed = true;
295 295 $.each(self.forbidReviewUsers, function(index, member_data) {
296 296 if (parseInt(userId) === member_data['user_id']) {
297 297 allowed = false;
298 298 return false // breaks the loop
299 299 }
300 300 });
301 301 return allowed
302 302 };
303 303
304 304 var userAllowed = userAllowedReview(id);
305 305 if (!userAllowed){
306 306 alert(_gettext('User `{0}` not allowed to be a reviewer').format(username));
307 307 } else {
308 308 // only add if it's not there
309 309 var alreadyReviewer = currentIds.indexOf('reviewer_'+id) != -1;
310 310
311 311 if (alreadyReviewer) {
312 312 alert(_gettext('User `{0}` already in reviewers').format(username));
313 313 } else {
314 314 members.innerHTML += renderTemplate('reviewMemberEntry', {
315 315 'member': reviewer_obj,
316 316 'mandatory': mandatory,
317 317 'allowed_to_update': true,
318 318 'review_status': 'not_reviewed',
319 319 'review_status_label': _gettext('Not Reviewed'),
320 320 'reasons': reasons,
321 321 'create': true
322 322 });
323 323 }
324 324 }
325 325
326 326 };
327 327
328 328 this.updateReviewers = function(repo_name, pull_request_id){
329 329 var postData = $('#reviewers input').serialize();
330 330 _updatePullRequest(repo_name, pull_request_id, postData);
331 331 };
332 332
333 333 };
334 334
335 335
336 336 var _updatePullRequest = function(repo_name, pull_request_id, postData) {
337 337 var url = pyroutes.url(
338 338 'pullrequest_update',
339 339 {"repo_name": repo_name, "pull_request_id": pull_request_id});
340 340 if (typeof postData === 'string' ) {
341 341 postData += '&csrf_token=' + CSRF_TOKEN;
342 342 } else {
343 343 postData.csrf_token = CSRF_TOKEN;
344 344 }
345 345 var success = function(o) {
346 346 window.location.reload();
347 347 };
348 348 ajaxPOST(url, postData, success);
349 349 };
350 350
351 351 /**
352 352 * PULL REQUEST update commits
353 353 */
354 354 var updateCommits = function(repo_name, pull_request_id) {
355 355 var postData = {
356 356 'update_commits': true};
357 357 _updatePullRequest(repo_name, pull_request_id, postData);
358 358 };
359 359
360 360
361 361 /**
362 362 * PULL REQUEST edit info
363 363 */
364 364 var editPullRequest = function(repo_name, pull_request_id, title, description) {
365 365 var url = pyroutes.url(
366 366 'pullrequest_update',
367 367 {"repo_name": repo_name, "pull_request_id": pull_request_id});
368 368
369 369 var postData = {
370 370 'title': title,
371 371 'description': description,
372 372 'edit_pull_request': true,
373 373 'csrf_token': CSRF_TOKEN
374 374 };
375 375 var success = function(o) {
376 376 window.location.reload();
377 377 };
378 378 ajaxPOST(url, postData, success);
379 379 };
380 380
381 var initPullRequestsCodeMirror = function (textAreaId) {
382 var ta = $(textAreaId).get(0);
383 var initialHeight = '100px';
384
385 // default options
386 var codeMirrorOptions = {
387 mode: "text",
388 lineNumbers: false,
389 indentUnit: 4,
390 theme: 'rc-input'
391 };
392
393 var codeMirrorInstance = CodeMirror.fromTextArea(ta, codeMirrorOptions);
394 // marker for manually set description
395 codeMirrorInstance._userDefinedDesc = false;
396 codeMirrorInstance.setSize(null, initialHeight);
397 codeMirrorInstance.on("change", function(instance, changeObj) {
398 var height = initialHeight;
399 var lines = instance.lineCount();
400 if (lines > 6 && lines < 20) {
401 height = "auto"
402 }
403 else if (lines >= 20) {
404 height = 20 * 15;
405 }
406 instance.setSize(null, height);
407
408 // detect if the change was trigger by auto desc, or user input
409 changeOrigin = changeObj.origin;
410
411 if (changeOrigin === "setValue") {
412 cmLog.debug('Change triggered by setValue');
413 }
414 else {
415 cmLog.debug('user triggered change !');
416 // set special marker to indicate user has created an input.
417 instance._userDefinedDesc = true;
418 }
419
420 });
421
422 return codeMirrorInstance
423 };
424 381
425 382 /**
426 383 * Reviewer autocomplete
427 384 */
428 385 var ReviewerAutoComplete = function(inputId) {
429 386 $(inputId).autocomplete({
430 387 serviceUrl: pyroutes.url('user_autocomplete_data'),
431 388 minChars:2,
432 389 maxHeight:400,
433 390 deferRequestBy: 300, //miliseconds
434 391 showNoSuggestionNotice: true,
435 392 tabDisabled: true,
436 393 autoSelectFirst: true,
437 394 params: { user_id: templateContext.rhodecode_user.user_id, user_groups:true, user_groups_expand:true, skip_default_user:true },
438 395 formatResult: autocompleteFormatResult,
439 396 lookupFilter: autocompleteFilterResult,
440 397 onSelect: function(element, data) {
441 398 var mandatory = false;
442 399 var reasons = [_gettext('added manually by "{0}"').format(templateContext.rhodecode_user.username)];
443 400
444 401 // add whole user groups
445 402 if (data.value_type == 'user_group') {
446 403 reasons.push(_gettext('member of "{0}"').format(data.value_display));
447 404
448 405 $.each(data.members, function(index, member_data) {
449 406 var reviewer = member_data;
450 407 reviewer['user_id'] = member_data['id'];
451 408 reviewer['gravatar_link'] = member_data['icon_link'];
452 409 reviewer['user_link'] = member_data['profile_link'];
453 410 reviewer['rules'] = [];
454 411 reviewersController.addReviewMember(reviewer, reasons, mandatory);
455 412 })
456 413 }
457 414 // add single user
458 415 else {
459 416 var reviewer = data;
460 417 reviewer['user_id'] = data['id'];
461 418 reviewer['gravatar_link'] = data['icon_link'];
462 419 reviewer['user_link'] = data['profile_link'];
463 420 reviewer['rules'] = [];
464 421 reviewersController.addReviewMember(reviewer, reasons, mandatory);
465 422 }
466 423
467 424 $(inputId).val('');
468 425 }
469 426 });
470 427 };
471 428
472 429
473 430 VersionController = function () {
474 431 var self = this;
475 432 this.$verSource = $('input[name=ver_source]');
476 433 this.$verTarget = $('input[name=ver_target]');
477 434 this.$showVersionDiff = $('#show-version-diff');
478 435
479 436 this.adjustRadioSelectors = function (curNode) {
480 437 var getVal = function (item) {
481 438 if (item == 'latest') {
482 439 return Number.MAX_SAFE_INTEGER
483 440 }
484 441 else {
485 442 return parseInt(item)
486 443 }
487 444 };
488 445
489 446 var curVal = getVal($(curNode).val());
490 447 var cleared = false;
491 448
492 449 $.each(self.$verSource, function (index, value) {
493 450 var elVal = getVal($(value).val());
494 451
495 452 if (elVal > curVal) {
496 453 if ($(value).is(':checked')) {
497 454 cleared = true;
498 455 }
499 456 $(value).attr('disabled', 'disabled');
500 457 $(value).removeAttr('checked');
501 458 $(value).css({'opacity': 0.1});
502 459 }
503 460 else {
504 461 $(value).css({'opacity': 1});
505 462 $(value).removeAttr('disabled');
506 463 }
507 464 });
508 465
509 466 if (cleared) {
510 467 // if we unchecked an active, set the next one to same loc.
511 468 $(this.$verSource).filter('[value={0}]'.format(
512 469 curVal)).attr('checked', 'checked');
513 470 }
514 471
515 472 self.setLockAction(false,
516 473 $(curNode).data('verPos'),
517 474 $(this.$verSource).filter(':checked').data('verPos')
518 475 );
519 476 };
520 477
521 478
522 479 this.attachVersionListener = function () {
523 480 self.$verTarget.change(function (e) {
524 481 self.adjustRadioSelectors(this)
525 482 });
526 483 self.$verSource.change(function (e) {
527 484 self.adjustRadioSelectors(self.$verTarget.filter(':checked'))
528 485 });
529 486 };
530 487
531 488 this.init = function () {
532 489
533 490 var curNode = self.$verTarget.filter(':checked');
534 491 self.adjustRadioSelectors(curNode);
535 492 self.setLockAction(true);
536 493 self.attachVersionListener();
537 494
538 495 };
539 496
540 497 this.setLockAction = function (state, selectedVersion, otherVersion) {
541 498 var $showVersionDiff = this.$showVersionDiff;
542 499
543 500 if (state) {
544 501 $showVersionDiff.attr('disabled', 'disabled');
545 502 $showVersionDiff.addClass('disabled');
546 503 $showVersionDiff.html($showVersionDiff.data('labelTextLocked'));
547 504 }
548 505 else {
549 506 $showVersionDiff.removeAttr('disabled');
550 507 $showVersionDiff.removeClass('disabled');
551 508
552 509 if (selectedVersion == otherVersion) {
553 510 $showVersionDiff.html($showVersionDiff.data('labelTextShow'));
554 511 } else {
555 512 $showVersionDiff.html($showVersionDiff.data('labelTextDiff'));
556 513 }
557 514 }
558 515
559 516 };
560 517
561 518 this.showVersionDiff = function () {
562 519 var target = self.$verTarget.filter(':checked');
563 520 var source = self.$verSource.filter(':checked');
564 521
565 522 if (target.val() && source.val()) {
566 523 var params = {
567 524 'pull_request_id': templateContext.pull_request_data.pull_request_id,
568 525 'repo_name': templateContext.repo_name,
569 526 'version': target.val(),
570 527 'from_version': source.val()
571 528 };
572 529 window.location = pyroutes.url('pullrequest_show', params)
573 530 }
574 531
575 532 return false;
576 533 };
577 534
578 535 this.toggleVersionView = function (elem) {
579 536
580 537 if (this.$showVersionDiff.is(':visible')) {
581 538 $('.version-pr').hide();
582 539 this.$showVersionDiff.hide();
583 540 $(elem).html($(elem).data('toggleOn'))
584 541 } else {
585 542 $('.version-pr').show();
586 543 this.$showVersionDiff.show();
587 544 $(elem).html($(elem).data('toggleOff'))
588 545 }
589 546
590 547 return false
591 548 }
592 549
593 550 }; No newline at end of file
@@ -1,377 +1,425 b''
1 1 ## DATA TABLE RE USABLE ELEMENTS
2 2 ## usage:
3 3 ## <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
4 4 <%namespace name="base" file="/base/base.mako"/>
5 5
6 6 <%def name="metatags_help()">
7 7 <table>
8 8 <%
9 9 example_tags = [
10 10 ('state','[stable]'),
11 11 ('state','[stale]'),
12 12 ('state','[featured]'),
13 13 ('state','[dev]'),
14 14 ('state','[dead]'),
15 15 ('state','[deprecated]'),
16 16
17 17 ('label','[personal]'),
18 18 ('generic','[v2.0.0]'),
19 19
20 20 ('lang','[lang =&gt; JavaScript]'),
21 21 ('license','[license =&gt; LicenseName]'),
22 22
23 23 ('ref','[requires =&gt; RepoName]'),
24 24 ('ref','[recommends =&gt; GroupName]'),
25 25 ('ref','[conflicts =&gt; SomeName]'),
26 26 ('ref','[base =&gt; SomeName]'),
27 27 ('url','[url =&gt; [linkName](https://rhodecode.com)]'),
28 28 ('see','[see =&gt; http://rhodecode.com]'),
29 29 ]
30 30 %>
31 31 % for tag_type, tag in example_tags:
32 32 <tr>
33 33 <td>${tag|n}</td>
34 34 <td>${h.style_metatag(tag_type, tag)|n}</td>
35 35 </tr>
36 36 % endfor
37 37 </table>
38 38 </%def>
39 39
40 40 ## REPOSITORY RENDERERS
41 41 <%def name="quick_menu(repo_name)">
42 42 <i class="icon-more"></i>
43 43 <div class="menu_items_container hidden">
44 44 <ul class="menu_items">
45 45 <li>
46 46 <a title="${_('Summary')}" href="${h.route_path('repo_summary',repo_name=repo_name)}">
47 47 <span>${_('Summary')}</span>
48 48 </a>
49 49 </li>
50 50 <li>
51 51 <a title="${_('Changelog')}" href="${h.route_path('repo_changelog',repo_name=repo_name)}">
52 52 <span>${_('Changelog')}</span>
53 53 </a>
54 54 </li>
55 55 <li>
56 56 <a title="${_('Files')}" href="${h.route_path('repo_files:default_commit',repo_name=repo_name)}">
57 57 <span>${_('Files')}</span>
58 58 </a>
59 59 </li>
60 60 <li>
61 61 <a title="${_('Fork')}" href="${h.route_path('repo_fork_new',repo_name=repo_name)}">
62 62 <span>${_('Fork')}</span>
63 63 </a>
64 64 </li>
65 65 </ul>
66 66 </div>
67 67 </%def>
68 68
69 69 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
70 70 <%
71 71 def get_name(name,short_name=short_name):
72 72 if short_name:
73 73 return name.split('/')[-1]
74 74 else:
75 75 return name
76 76 %>
77 77 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
78 78 ##NAME
79 79 <a href="${h.route_path('edit_repo',repo_name=name) if admin else h.route_path('repo_summary',repo_name=name)}">
80 80
81 81 ##TYPE OF REPO
82 82 %if h.is_hg(rtype):
83 83 <span title="${_('Mercurial repository')}"><i class="icon-hg" style="font-size: 14px;"></i></span>
84 84 %elif h.is_git(rtype):
85 85 <span title="${_('Git repository')}"><i class="icon-git" style="font-size: 14px"></i></span>
86 86 %elif h.is_svn(rtype):
87 87 <span title="${_('Subversion repository')}"><i class="icon-svn" style="font-size: 14px"></i></span>
88 88 %endif
89 89
90 90 ##PRIVATE/PUBLIC
91 91 %if private and c.visual.show_private_icon:
92 92 <i class="icon-lock" title="${_('Private repository')}"></i>
93 93 %elif not private and c.visual.show_public_icon:
94 94 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
95 95 %else:
96 96 <span></span>
97 97 %endif
98 98 ${get_name(name)}
99 99 </a>
100 100 %if fork_of:
101 101 <a href="${h.route_path('repo_summary',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
102 102 %endif
103 103 %if rstate == 'repo_state_pending':
104 104 <span class="creation_in_progress tooltip" title="${_('This repository is being created in a background task')}">
105 105 (${_('creating...')})
106 106 </span>
107 107 %endif
108 108 </div>
109 109 </%def>
110 110
111 111 <%def name="repo_desc(description, stylify_metatags)">
112 112 <%
113 113 tags, description = h.extract_metatags(description)
114 114 %>
115 115
116 116 <div class="truncate-wrap">
117 117 % if stylify_metatags:
118 118 % for tag_type, tag in tags:
119 119 ${h.style_metatag(tag_type, tag)|n}
120 120 % endfor
121 121 % endif
122 122 ${description}
123 123 </div>
124 124
125 125 </%def>
126 126
127 127 <%def name="last_change(last_change)">
128 128 ${h.age_component(last_change, time_is_local=True)}
129 129 </%def>
130 130
131 131 <%def name="revision(name,rev,tip,author,last_msg, commit_date)">
132 132 <div>
133 133 %if rev >= 0:
134 134 <code><a title="${h.tooltip('%s\n%s\n\n%s' % (author, commit_date, last_msg))}" class="tooltip" href="${h.route_path('repo_commit',repo_name=name,commit_id=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
135 135 %else:
136 136 ${_('No commits yet')}
137 137 %endif
138 138 </div>
139 139 </%def>
140 140
141 141 <%def name="rss(name)">
142 142 %if c.rhodecode_user.username != h.DEFAULT_USER:
143 143 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
144 144 %else:
145 145 <a title="${h.tooltip(_('Subscribe to %s rss feed')% name)}" href="${h.route_path('rss_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
146 146 %endif
147 147 </%def>
148 148
149 149 <%def name="atom(name)">
150 150 %if c.rhodecode_user.username != h.DEFAULT_USER:
151 151 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name, _query=dict(auth_token=c.rhodecode_user.feed_token))}"><i class="icon-rss-sign"></i></a>
152 152 %else:
153 153 <a title="${h.tooltip(_('Subscribe to %s atom feed')% name)}" href="${h.route_path('atom_feed_home', repo_name=name)}"><i class="icon-rss-sign"></i></a>
154 154 %endif
155 155 </%def>
156 156
157 157 <%def name="user_gravatar(email, size=16)">
158 158 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
159 159 ${base.gravatar(email, 16)}
160 160 </div>
161 161 </%def>
162 162
163 163 <%def name="repo_actions(repo_name, super_user=True)">
164 164 <div>
165 165 <div class="grid_edit">
166 166 <a href="${h.route_path('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
167 167 <i class="icon-pencil"></i>Edit</a>
168 168 </div>
169 169 <div class="grid_delete">
170 170 ${h.secure_form(h.route_path('edit_repo_advanced_delete', repo_name=repo_name), request=request)}
171 171 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
172 172 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
173 173 ${h.end_form()}
174 174 </div>
175 175 </div>
176 176 </%def>
177 177
178 178 <%def name="repo_state(repo_state)">
179 179 <div>
180 180 %if repo_state == 'repo_state_pending':
181 181 <div class="tag tag4">${_('Creating')}</div>
182 182 %elif repo_state == 'repo_state_created':
183 183 <div class="tag tag1">${_('Created')}</div>
184 184 %else:
185 185 <div class="tag alert2" title="${h.tooltip(repo_state)}">invalid</div>
186 186 %endif
187 187 </div>
188 188 </%def>
189 189
190 190
191 191 ## REPO GROUP RENDERERS
192 192 <%def name="quick_repo_group_menu(repo_group_name)">
193 193 <i class="icon-more"></i>
194 194 <div class="menu_items_container hidden">
195 195 <ul class="menu_items">
196 196 <li>
197 197 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">${_('Summary')}</a>
198 198 </li>
199 199
200 200 </ul>
201 201 </div>
202 202 </%def>
203 203
204 204 <%def name="repo_group_name(repo_group_name, children_groups=None)">
205 205 <div>
206 206 <a href="${h.route_path('repo_group_home', repo_group_name=repo_group_name)}">
207 207 <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i>
208 208 %if children_groups:
209 209 ${h.literal(' &raquo; '.join(children_groups))}
210 210 %else:
211 211 ${repo_group_name}
212 212 %endif
213 213 </a>
214 214 </div>
215 215 </%def>
216 216
217 217 <%def name="repo_group_desc(description, personal, stylify_metatags)">
218 218
219 219 <%
220 220 tags, description = h.extract_metatags(description)
221 221 %>
222 222
223 223 <div class="truncate-wrap">
224 224 % if personal:
225 225 <div class="metatag" tag="personal">${_('personal')}</div>
226 226 % endif
227 227
228 228 % if stylify_metatags:
229 229 % for tag_type, tag in tags:
230 230 ${h.style_metatag(tag_type, tag)|n}
231 231 % endfor
232 232 % endif
233 233 ${description}
234 234 </div>
235 235
236 236 </%def>
237 237
238 238 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
239 239 <div class="grid_edit">
240 240 <a href="${h.route_path('edit_repo_group',repo_group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
241 241 </div>
242 242 <div class="grid_delete">
243 243 ${h.secure_form(h.route_path('edit_repo_group_advanced_delete', repo_group_name=repo_group_name), request=request)}
244 244 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
245 245 onclick="return confirm('"+_ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
246 246 ${h.end_form()}
247 247 </div>
248 248 </%def>
249 249
250 250
251 251 <%def name="user_actions(user_id, username)">
252 252 <div class="grid_edit">
253 253 <a href="${h.route_path('user_edit',user_id=user_id)}" title="${_('Edit')}">
254 254 <i class="icon-pencil"></i>${_('Edit')}</a>
255 255 </div>
256 256 <div class="grid_delete">
257 257 ${h.secure_form(h.route_path('user_delete', user_id=user_id), request=request)}
258 258 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
259 259 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
260 260 ${h.end_form()}
261 261 </div>
262 262 </%def>
263 263
264 264 <%def name="user_group_actions(user_group_id, user_group_name)">
265 265 <div class="grid_edit">
266 266 <a href="${h.route_path('edit_user_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
267 267 </div>
268 268 <div class="grid_delete">
269 269 ${h.secure_form(h.route_path('user_groups_delete', user_group_id=user_group_id), request=request)}
270 270 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
271 271 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
272 272 ${h.end_form()}
273 273 </div>
274 274 </%def>
275 275
276 276
277 277 <%def name="user_name(user_id, username)">
278 278 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.route_path('user_edit', user_id=user_id))}
279 279 </%def>
280 280
281 281 <%def name="user_profile(username)">
282 282 ${base.gravatar_with_user(username, 16)}
283 283 </%def>
284 284
285 285 <%def name="user_group_name(user_group_name)">
286 286 <div>
287 287 <i class="icon-group" title="${_('User group')}"></i>
288 288 ${h.link_to_group(user_group_name)}
289 289 </div>
290 290 </%def>
291 291
292 292
293 293 ## GISTS
294 294
295 295 <%def name="gist_gravatar(full_contact)">
296 296 <div class="gist_gravatar">
297 297 ${base.gravatar(full_contact, 30)}
298 298 </div>
299 299 </%def>
300 300
301 301 <%def name="gist_access_id(gist_access_id, full_contact)">
302 302 <div>
303 303 <b>
304 304 <a href="${h.route_path('gist_show', gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
305 305 </b>
306 306 </div>
307 307 </%def>
308 308
309 309 <%def name="gist_author(full_contact, created_on, expires)">
310 310 ${base.gravatar_with_user(full_contact, 16)}
311 311 </%def>
312 312
313 313
314 314 <%def name="gist_created(created_on)">
315 315 <div class="created">
316 316 ${h.age_component(created_on, time_is_local=True)}
317 317 </div>
318 318 </%def>
319 319
320 320 <%def name="gist_expires(expires)">
321 321 <div class="created">
322 322 %if expires == -1:
323 323 ${_('never')}
324 324 %else:
325 325 ${h.age_component(h.time_to_utcdatetime(expires))}
326 326 %endif
327 327 </div>
328 328 </%def>
329 329
330 330 <%def name="gist_type(gist_type)">
331 331 %if gist_type != 'public':
332 332 <div class="tag">${_('Private')}</div>
333 333 %endif
334 334 </%def>
335 335
336 336 <%def name="gist_description(gist_description)">
337 337 ${gist_description}
338 338 </%def>
339 339
340 340
341 341 ## PULL REQUESTS GRID RENDERERS
342 342
343 343 <%def name="pullrequest_target_repo(repo_name)">
344 344 <div class="truncate">
345 345 ${h.link_to(repo_name,h.route_path('repo_summary',repo_name=repo_name))}
346 346 </div>
347 347 </%def>
348 348 <%def name="pullrequest_status(status)">
349 349 <div class="${'flag_status %s' % status} pull-left"></div>
350 350 </%def>
351 351
352 352 <%def name="pullrequest_title(title, description)">
353 ${title} <br/>
354 ${h.shorter(description, 40)}
353 ${title}
355 354 </%def>
356 355
357 356 <%def name="pullrequest_comments(comments_nr)">
358 357 <i class="icon-comment"></i> ${comments_nr}
359 358 </%def>
360 359
361 360 <%def name="pullrequest_name(pull_request_id, target_repo_name, short=False)">
362 361 <a href="${h.route_path('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
363 362 % if short:
364 363 #${pull_request_id}
365 364 % else:
366 365 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
367 366 % endif
368 367 </a>
369 368 </%def>
370 369
371 370 <%def name="pullrequest_updated_on(updated_on)">
372 371 ${h.age_component(h.time_to_utcdatetime(updated_on))}
373 372 </%def>
374 373
375 374 <%def name="pullrequest_author(full_contact)">
376 375 ${base.gravatar_with_user(full_contact, 16)}
377 376 </%def>
377
378
379 <%def name="markup_form(form_id, form_text='', help_text=None)">
380
381 <div class="markup-form">
382 <div class="markup-form-area">
383 <div class="markup-form-area-header">
384 <ul class="nav-links clearfix">
385 <li class="active">
386 <a href="#edit-text" tabindex="-1" id="edit-btn_${form_id}">${_('Write')}</a>
387 </li>
388 <li class="">
389 <a href="#preview-text" tabindex="-1" id="preview-btn_${form_id}">${_('Preview')}</a>
390 </li>
391 </ul>
392 </div>
393
394 <div class="markup-form-area-write" style="display: block;">
395 <div id="edit-container_${form_id}">
396 <textarea id="${form_id}" name="${form_id}" class="comment-block-ta ac-input">${form_text if form_text else ''}</textarea>
397 </div>
398 <div id="preview-container_${form_id}" class="clearfix" style="display: none;">
399 <div id="preview-box_${form_id}" class="preview-box"></div>
400 </div>
401 </div>
402
403 <div class="markup-form-area-footer">
404 <div class="toolbar">
405 <div class="toolbar-text">
406 ${(_('Parsed using %s syntax') % (
407 ('<a href="%s">%s</a>' % (h.route_url('%s_help' % c.visual.default_renderer), c.visual.default_renderer.upper())),
408 )
409 )|n}
410 </div>
411 </div>
412 </div>
413 </div>
414
415 <div class="markup-form-footer">
416 % if help_text:
417 <span class="help-block">${help_text}</span>
418 % endif
419 </div>
420 </div>
421 <script type="text/javascript">
422 new MarkupForm('${form_id}');
423 </script>
424
425 </%def>
This diff has been collapsed as it changes many lines, (633 lines changed) Show them Hide them
@@ -1,542 +1,547 b''
1 1 <%inherit file="/base/base.mako"/>
2 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
2 3
3 4 <%def name="title()">
4 5 ${c.repo_name} ${_('New pull request')}
5 6 </%def>
6 7
7 8 <%def name="breadcrumbs_links()">
8 9 ${_('New pull request')}
9 10 </%def>
10 11
11 12 <%def name="menu_bar_nav()">
12 13 ${self.menu_items(active='repositories')}
13 14 </%def>
14 15
15 16 <%def name="menu_bar_subnav()">
16 17 ${self.repo_menu(active='showpullrequest')}
17 18 </%def>
18 19
19 20 <%def name="main()">
20 21 <div class="box">
21 22 <div class="title">
22 23 ${self.repo_page_title(c.rhodecode_db_repo)}
23 24 </div>
24 25
25 26 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
26 27
27 28 ${self.breadcrumbs()}
28 29
29 30 <div class="box pr-summary">
30 31
31 32 <div class="summary-details block-left">
32 33
33 34
34 35 <div class="pr-details-title">
35 36 ${_('Pull request summary')}
36 37 </div>
37 38
38 39 <div class="form" style="padding-top: 10px">
39 40 <!-- fields -->
40 41
41 42 <div class="fields" >
42 43
43 44 <div class="field">
44 45 <div class="label">
45 46 <label for="pullrequest_title">${_('Title')}:</label>
46 47 </div>
47 48 <div class="input">
48 49 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
49 50 </div>
50 51 </div>
51 52
52 53 <div class="field">
53 54 <div class="label label-textarea">
54 55 <label for="pullrequest_desc">${_('Description')}:</label>
55 56 </div>
56 57 <div class="textarea text-area editor">
57 ${h.textarea('pullrequest_desc',size=30, )}
58 <span class="help-block">${_('Write a short description on this pull request')}</span>
58 ${dt.markup_form('pullrequest_desc')}
59 59 </div>
60 60 </div>
61 61
62 62 <div class="field">
63 63 <div class="label label-textarea">
64 <label for="pullrequest_desc">${_('Commit flow')}:</label>
64 <label for="commit_flow">${_('Commit flow')}:</label>
65 65 </div>
66 66
67 67 ## TODO: johbo: Abusing the "content" class here to get the
68 68 ## desired effect. Should be replaced by a proper solution.
69 69
70 70 ##ORG
71 71 <div class="content">
72 72 <strong>${_('Source repository')}:</strong>
73 73 ${c.rhodecode_db_repo.description}
74 74 </div>
75 75 <div class="content">
76 76 ${h.hidden('source_repo')}
77 77 ${h.hidden('source_ref')}
78 78 </div>
79 79
80 80 ##OTHER, most Probably the PARENT OF THIS FORK
81 81 <div class="content">
82 82 ## filled with JS
83 83 <div id="target_repo_desc"></div>
84 84 </div>
85 85
86 86 <div class="content">
87 87 ${h.hidden('target_repo')}
88 88 ${h.hidden('target_ref')}
89 89 <span id="target_ref_loading" style="display: none">
90 90 ${_('Loading refs...')}
91 91 </span>
92 92 </div>
93 93 </div>
94 94
95 95 <div class="field">
96 96 <div class="label label-textarea">
97 97 <label for="pullrequest_submit"></label>
98 98 </div>
99 99 <div class="input">
100 100 <div class="pr-submit-button">
101 101 <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
102 102 </div>
103 103 <div id="pr_open_message"></div>
104 104 </div>
105 105 </div>
106 106
107 107 <div class="pr-spacing-container"></div>
108 108 </div>
109 109 </div>
110 110 </div>
111 111 <div>
112 112 ## AUTHOR
113 113 <div class="reviewers-title block-right">
114 114 <div class="pr-details-title">
115 115 ${_('Author of this pull request')}
116 116 </div>
117 117 </div>
118 118 <div class="block-right pr-details-content reviewers">
119 119 <ul class="group_members">
120 120 <li>
121 121 ${self.gravatar_with_user(c.rhodecode_user.email, 16)}
122 122 </li>
123 123 </ul>
124 124 </div>
125 125
126 126 ## REVIEW RULES
127 127 <div id="review_rules" style="display: none" class="reviewers-title block-right">
128 128 <div class="pr-details-title">
129 129 ${_('Reviewer rules')}
130 130 </div>
131 131 <div class="pr-reviewer-rules">
132 132 ## review rules will be appended here, by default reviewers logic
133 133 </div>
134 134 </div>
135 135
136 136 ## REVIEWERS
137 137 <div class="reviewers-title block-right">
138 138 <div class="pr-details-title">
139 139 ${_('Pull request reviewers')}
140 140 <span class="calculate-reviewers"> - ${_('loading...')}</span>
141 141 </div>
142 142 </div>
143 143 <div id="reviewers" class="block-right pr-details-content reviewers">
144 144 ## members goes here, filled via JS based on initial selection !
145 145 <input type="hidden" name="__start__" value="review_members:sequence">
146 146 <ul id="review_members" class="group_members"></ul>
147 147 <input type="hidden" name="__end__" value="review_members:sequence">
148 148 <div id="add_reviewer_input" class='ac'>
149 149 <div class="reviewer_ac">
150 150 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
151 151 <div id="reviewers_container"></div>
152 152 </div>
153 153 </div>
154 154 </div>
155 155 </div>
156 156 </div>
157 157 <div class="box">
158 158 <div>
159 159 ## overview pulled by ajax
160 160 <div id="pull_request_overview"></div>
161 161 </div>
162 162 </div>
163 163 ${h.end_form()}
164 164 </div>
165 165
166 166 <script type="text/javascript">
167 $(function(){
168 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
169 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
170 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
171 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
167 $(function(){
168 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
169 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
170 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
171 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
172 172
173 var $pullRequestForm = $('#pull_request_form');
174 var $pullRequestSubmit = $('#pr_submit', $pullRequestForm);
175 var $sourceRepo = $('#source_repo', $pullRequestForm);
176 var $targetRepo = $('#target_repo', $pullRequestForm);
177 var $sourceRef = $('#source_ref', $pullRequestForm);
178 var $targetRef = $('#target_ref', $pullRequestForm);
173 var $pullRequestForm = $('#pull_request_form');
174 var $pullRequestSubmit = $('#pr_submit', $pullRequestForm);
175 var $sourceRepo = $('#source_repo', $pullRequestForm);
176 var $targetRepo = $('#target_repo', $pullRequestForm);
177 var $sourceRef = $('#source_ref', $pullRequestForm);
178 var $targetRef = $('#target_ref', $pullRequestForm);
179 179
180 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
181 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
180 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
181 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
182 182
183 var targetRepo = function() { return $targetRepo.eq(0).val() };
184 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
183 var targetRepo = function() { return $targetRepo.eq(0).val() };
184 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
185 185
186 var calculateContainerWidth = function() {
187 var maxWidth = 0;
188 var repoSelect2Containers = ['#source_repo', '#target_repo'];
189 $.each(repoSelect2Containers, function(idx, value) {
190 $(value).select2('container').width('auto');
191 var curWidth = $(value).select2('container').width();
192 if (maxWidth <= curWidth) {
193 maxWidth = curWidth;
194 }
195 $.each(repoSelect2Containers, function(idx, value) {
196 $(value).select2('container').width(maxWidth + 10);
197 });
198 });
199 };
186 var calculateContainerWidth = function() {
187 var maxWidth = 0;
188 var repoSelect2Containers = ['#source_repo', '#target_repo'];
189 $.each(repoSelect2Containers, function(idx, value) {
190 $(value).select2('container').width('auto');
191 var curWidth = $(value).select2('container').width();
192 if (maxWidth <= curWidth) {
193 maxWidth = curWidth;
194 }
195 $.each(repoSelect2Containers, function(idx, value) {
196 $(value).select2('container').width(maxWidth + 10);
197 });
198 });
199 };
200 200
201 var initRefSelection = function(selectedRef) {
202 return function(element, callback) {
203 // translate our select2 id into a text, it's a mapping to show
204 // simple label when selecting by internal ID.
205 var id, refData;
206 if (selectedRef === undefined || selectedRef === null) {
207 id = element.val();
208 refData = element.val().split(':');
201 var initRefSelection = function(selectedRef) {
202 return function(element, callback) {
203 // translate our select2 id into a text, it's a mapping to show
204 // simple label when selecting by internal ID.
205 var id, refData;
206 if (selectedRef === undefined || selectedRef === null) {
207 id = element.val();
208 refData = element.val().split(':');
209 209
210 if (refData.length !== 3){
211 refData = ["", "", ""]
212 }
213 } else {
214 id = selectedRef;
215 refData = selectedRef.split(':');
216 }
210 if (refData.length !== 3){
211 refData = ["", "", ""]
212 }
213 } else {
214 id = selectedRef;
215 refData = selectedRef.split(':');
216 }
217 217
218 var text = refData[1];
219 if (refData[0] === 'rev') {
220 text = text.substring(0, 12);
221 }
218 var text = refData[1];
219 if (refData[0] === 'rev') {
220 text = text.substring(0, 12);
221 }
222 222
223 var data = {id: id, text: text};
224 callback(data);
225 };
226 };
223 var data = {id: id, text: text};
224 callback(data);
225 };
226 };
227 227
228 var formatRefSelection = function(item) {
229 var prefix = '';
230 var refData = item.id.split(':');
231 if (refData[0] === 'branch') {
232 prefix = '<i class="icon-branch"></i>';
233 }
234 else if (refData[0] === 'book') {
235 prefix = '<i class="icon-bookmark"></i>';
236 }
237 else if (refData[0] === 'tag') {
238 prefix = '<i class="icon-tag"></i>';
239 }
228 var formatRefSelection = function(item) {
229 var prefix = '';
230 var refData = item.id.split(':');
231 if (refData[0] === 'branch') {
232 prefix = '<i class="icon-branch"></i>';
233 }
234 else if (refData[0] === 'book') {
235 prefix = '<i class="icon-bookmark"></i>';
236 }
237 else if (refData[0] === 'tag') {
238 prefix = '<i class="icon-tag"></i>';
239 }
240 240
241 var originalOption = item.element;
242 return prefix + item.text;
243 };
241 var originalOption = item.element;
242 return prefix + item.text;
243 };
244 244
245 // custom code mirror
246 var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc');
245 // custom code mirror
246 var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm;
247 247
248 reviewersController = new ReviewersController();
248 reviewersController = new ReviewersController();
249 249
250 var queryTargetRepo = function(self, query) {
251 // cache ALL results if query is empty
252 var cacheKey = query.term || '__';
253 var cachedData = self.cachedDataSource[cacheKey];
250 var queryTargetRepo = function(self, query) {
251 // cache ALL results if query is empty
252 var cacheKey = query.term || '__';
253 var cachedData = self.cachedDataSource[cacheKey];
254 254
255 if (cachedData) {
256 query.callback({results: cachedData.results});
257 } else {
258 $.ajax({
259 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}),
260 data: {query: query.term},
261 dataType: 'json',
262 type: 'GET',
263 success: function(data) {
264 self.cachedDataSource[cacheKey] = data;
265 query.callback({results: data.results});
266 },
267 error: function(data, textStatus, errorThrown) {
268 alert(
269 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
270 }
271 });
272 }
273 };
255 if (cachedData) {
256 query.callback({results: cachedData.results});
257 } else {
258 $.ajax({
259 url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}),
260 data: {query: query.term},
261 dataType: 'json',
262 type: 'GET',
263 success: function(data) {
264 self.cachedDataSource[cacheKey] = data;
265 query.callback({results: data.results});
266 },
267 error: function(data, textStatus, errorThrown) {
268 alert(
269 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
270 }
271 });
272 }
273 };
274 274
275 var queryTargetRefs = function(initialData, query) {
276 var data = {results: []};
277 // filter initialData
278 $.each(initialData, function() {
279 var section = this.text;
280 var children = [];
281 $.each(this.children, function() {
282 if (query.term.length === 0 ||
283 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
284 children.push({'id': this.id, 'text': this.text})
285 }
286 });
287 data.results.push({'text': section, 'children': children})
288 });
289 query.callback({results: data.results});
290 };
275 var queryTargetRefs = function(initialData, query) {
276 var data = {results: []};
277 // filter initialData
278 $.each(initialData, function() {
279 var section = this.text;
280 var children = [];
281 $.each(this.children, function() {
282 if (query.term.length === 0 ||
283 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
284 children.push({'id': this.id, 'text': this.text})
285 }
286 });
287 data.results.push({'text': section, 'children': children})
288 });
289 query.callback({results: data.results});
290 };
291 291
292 var loadRepoRefDiffPreview = function() {
292 var loadRepoRefDiffPreview = function() {
293 293
294 var url_data = {
295 'repo_name': targetRepo(),
296 'target_repo': sourceRepo(),
297 'source_ref': targetRef()[2],
298 'source_ref_type': 'rev',
299 'target_ref': sourceRef()[2],
300 'target_ref_type': 'rev',
301 'merge': true,
302 '_': Date.now() // bypass browser caching
303 }; // gather the source/target ref and repo here
294 var url_data = {
295 'repo_name': targetRepo(),
296 'target_repo': sourceRepo(),
297 'source_ref': targetRef()[2],
298 'source_ref_type': 'rev',
299 'target_ref': sourceRef()[2],
300 'target_ref_type': 'rev',
301 'merge': true,
302 '_': Date.now() // bypass browser caching
303 }; // gather the source/target ref and repo here
304 304
305 if (sourceRef().length !== 3 || targetRef().length !== 3) {
306 prButtonLock(true, "${_('Please select source and target')}");
307 return;
308 }
309 var url = pyroutes.url('repo_compare', url_data);
305 if (sourceRef().length !== 3 || targetRef().length !== 3) {
306 prButtonLock(true, "${_('Please select source and target')}");
307 return;
308 }
309 var url = pyroutes.url('repo_compare', url_data);
310 310
311 // lock PR button, so we cannot send PR before it's calculated
312 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
311 // lock PR button, so we cannot send PR before it's calculated
312 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
313
314 if (loadRepoRefDiffPreview._currentRequest) {
315 loadRepoRefDiffPreview._currentRequest.abort();
316 }
313 317
314 if (loadRepoRefDiffPreview._currentRequest) {
315 loadRepoRefDiffPreview._currentRequest.abort();
316 }
318 loadRepoRefDiffPreview._currentRequest = $.get(url)
319 .error(function(data, textStatus, errorThrown) {
320 if (textStatus !== 'abort') {
321 alert(
322 "Error while processing request.\nError code {0} ({1}).".format(
323 data.status, data.statusText));
324 }
317 325
318 loadRepoRefDiffPreview._currentRequest = $.get(url)
319 .error(function(data, textStatus, errorThrown) {
320 if (textStatus !== 'abort') {
321 alert(
322 "Error while processing request.\nError code {0} ({1}).".format(
323 data.status, data.statusText));
324 }
326 })
327 .done(function(data) {
328 loadRepoRefDiffPreview._currentRequest = null;
329 $('#pull_request_overview').html(data);
330
331 var commitElements = $(data).find('tr[commit_id]');
325 332
326 })
327 .done(function(data) {
328 loadRepoRefDiffPreview._currentRequest = null;
329 $('#pull_request_overview').html(data);
333 var prTitleAndDesc = getTitleAndDescription(
334 sourceRef()[1], commitElements, 5);
330 335
331 var commitElements = $(data).find('tr[commit_id]');
336 var title = prTitleAndDesc[0];
337 var proposedDescription = prTitleAndDesc[1];
332 338
333 var prTitleAndDesc = getTitleAndDescription(
334 sourceRef()[1], commitElements, 5);
339 var useGeneratedTitle = (
340 $('#pullrequest_title').hasClass('autogenerated-title') ||
341 $('#pullrequest_title').val() === "");
335 342
336 var title = prTitleAndDesc[0];
337 var proposedDescription = prTitleAndDesc[1];
338
339 var useGeneratedTitle = (
340 $('#pullrequest_title').hasClass('autogenerated-title') ||
341 $('#pullrequest_title').val() === "");
343 if (title && useGeneratedTitle) {
344 // use generated title if we haven't specified our own
345 $('#pullrequest_title').val(title);
346 $('#pullrequest_title').addClass('autogenerated-title');
342 347
343 if (title && useGeneratedTitle) {
344 // use generated title if we haven't specified our own
345 $('#pullrequest_title').val(title);
346 $('#pullrequest_title').addClass('autogenerated-title');
348 }
349
350 var useGeneratedDescription = (
351 !codeMirrorInstance._userDefinedValue ||
352 codeMirrorInstance.getValue() === "");
347 353
348 }
354 if (proposedDescription && useGeneratedDescription) {
355 // set proposed content, if we haven't defined our own,
356 // or we don't have description written
357 codeMirrorInstance._userDefinedValue = false; // reset state
358 codeMirrorInstance.setValue(proposedDescription);
359 }
349 360
350 var useGeneratedDescription = (
351 !codeMirrorInstance._userDefinedDesc ||
352 codeMirrorInstance.getValue() === "");
361 // refresh our codeMirror so events kicks in and it's change aware
362 codeMirrorInstance.refresh();
353 363
354 if (proposedDescription && useGeneratedDescription) {
355 // set proposed content, if we haven't defined our own,
356 // or we don't have description written
357 codeMirrorInstance._userDefinedDesc = false; // reset state
358 codeMirrorInstance.setValue(proposedDescription);
359 }
364 var msg = '';
365 if (commitElements.length === 1) {
366 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
367 } else {
368 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
369 }
360 370
361 var msg = '';
362 if (commitElements.length === 1) {
363 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
364 } else {
365 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
366 }
371 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
367 372
368 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
369
370 if (commitElements.length) {
371 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
372 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
373 }
374 else {
375 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
376 }
373 if (commitElements.length) {
374 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
375 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
376 }
377 else {
378 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
379 }
377 380
378 381
379 });
380 };
382 });
383 };
381 384
382 var Select2Box = function(element, overrides) {
383 var globalDefaults = {
384 dropdownAutoWidth: true,
385 containerCssClass: "drop-menu",
386 dropdownCssClass: "drop-menu-dropdown"
387 };
385 var Select2Box = function(element, overrides) {
386 var globalDefaults = {
387 dropdownAutoWidth: true,
388 containerCssClass: "drop-menu",
389 dropdownCssClass: "drop-menu-dropdown"
390 };
388 391
389 var initSelect2 = function(defaultOptions) {
390 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
391 element.select2(options);
392 };
392 var initSelect2 = function(defaultOptions) {
393 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
394 element.select2(options);
395 };
393 396
394 return {
395 initRef: function() {
396 var defaultOptions = {
397 minimumResultsForSearch: 5,
398 formatSelection: formatRefSelection
399 };
397 return {
398 initRef: function() {
399 var defaultOptions = {
400 minimumResultsForSearch: 5,
401 formatSelection: formatRefSelection
402 };
400 403
401 initSelect2(defaultOptions);
402 },
404 initSelect2(defaultOptions);
405 },
403 406
404 initRepo: function(defaultValue, readOnly) {
405 var defaultOptions = {
406 initSelection : function (element, callback) {
407 var data = {id: defaultValue, text: defaultValue};
408 callback(data);
409 }
410 };
407 initRepo: function(defaultValue, readOnly) {
408 var defaultOptions = {
409 initSelection : function (element, callback) {
410 var data = {id: defaultValue, text: defaultValue};
411 callback(data);
412 }
413 };
411 414
412 initSelect2(defaultOptions);
415 initSelect2(defaultOptions);
413 416
414 element.select2('val', defaultSourceRepo);
415 if (readOnly === true) {
416 element.select2('readonly', true);
417 }
418 }
419 };
420 };
417 element.select2('val', defaultSourceRepo);
418 if (readOnly === true) {
419 element.select2('readonly', true);
420 }
421 }
422 };
423 };
421 424
422 var initTargetRefs = function(refsData, selectedRef) {
425 var initTargetRefs = function(refsData, selectedRef) {
423 426
424 Select2Box($targetRef, {
425 placeholder: "${_('Select commit reference')}",
426 query: function(query) {
427 queryTargetRefs(refsData, query);
428 },
429 initSelection : initRefSelection(selectedRef)
430 }).initRef();
427 Select2Box($targetRef, {
428 placeholder: "${_('Select commit reference')}",
429 query: function(query) {
430 queryTargetRefs(refsData, query);
431 },
432 initSelection : initRefSelection(selectedRef)
433 }).initRef();
431 434
432 if (!(selectedRef === undefined)) {
433 $targetRef.select2('val', selectedRef);
434 }
435 };
435 if (!(selectedRef === undefined)) {
436 $targetRef.select2('val', selectedRef);
437 }
438 };
436 439
437 var targetRepoChanged = function(repoData) {
438 // generate new DESC of target repo displayed next to select
439 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
440 $('#target_repo_desc').html(
441 "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
442 );
440 var targetRepoChanged = function(repoData) {
441 // generate new DESC of target repo displayed next to select
442 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
443 $('#target_repo_desc').html(
444 "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
445 );
443 446
444 // generate dynamic select2 for refs.
445 initTargetRefs(repoData['refs']['select2_refs'],
446 repoData['refs']['selected_ref']);
447 // generate dynamic select2 for refs.
448 initTargetRefs(repoData['refs']['select2_refs'],
449 repoData['refs']['selected_ref']);
447 450
448 };
451 };
449 452
450 var sourceRefSelect2 = Select2Box($sourceRef, {
451 placeholder: "${_('Select commit reference')}",
452 query: function(query) {
453 var initialData = defaultSourceRepoData['refs']['select2_refs'];
454 queryTargetRefs(initialData, query)
455 },
456 initSelection: initRefSelection()
457 }
458 );
453 var sourceRefSelect2 = Select2Box($sourceRef, {
454 placeholder: "${_('Select commit reference')}",
455 query: function(query) {
456 var initialData = defaultSourceRepoData['refs']['select2_refs'];
457 queryTargetRefs(initialData, query)
458 },
459 initSelection: initRefSelection()
460 }
461 );
459 462
460 var sourceRepoSelect2 = Select2Box($sourceRepo, {
461 query: function(query) {}
462 });
463 var sourceRepoSelect2 = Select2Box($sourceRepo, {
464 query: function(query) {}
465 });
463 466
464 var targetRepoSelect2 = Select2Box($targetRepo, {
465 cachedDataSource: {},
466 query: $.debounce(250, function(query) {
467 queryTargetRepo(this, query);
468 }),
469 formatResult: formatRepoResult
470 });
467 var targetRepoSelect2 = Select2Box($targetRepo, {
468 cachedDataSource: {},
469 query: $.debounce(250, function(query) {
470 queryTargetRepo(this, query);
471 }),
472 formatResult: formatRepoResult
473 });
471 474
472 sourceRefSelect2.initRef();
475 sourceRefSelect2.initRef();
473 476
474 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
477 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
475 478
476 targetRepoSelect2.initRepo(defaultTargetRepo, false);
479 targetRepoSelect2.initRepo(defaultTargetRepo, false);
477 480
478 $sourceRef.on('change', function(e){
479 loadRepoRefDiffPreview();
480 reviewersController.loadDefaultReviewers(
481 sourceRepo(), sourceRef(), targetRepo(), targetRef());
482 });
481 $sourceRef.on('change', function(e){
482 loadRepoRefDiffPreview();
483 reviewersController.loadDefaultReviewers(
484 sourceRepo(), sourceRef(), targetRepo(), targetRef());
485 });
483 486
484 $targetRef.on('change', function(e){
485 loadRepoRefDiffPreview();
486 reviewersController.loadDefaultReviewers(
487 sourceRepo(), sourceRef(), targetRepo(), targetRef());
488 });
487 $targetRef.on('change', function(e){
488 loadRepoRefDiffPreview();
489 reviewersController.loadDefaultReviewers(
490 sourceRepo(), sourceRef(), targetRepo(), targetRef());
491 });
489 492
490 $targetRepo.on('change', function(e){
491 var repoName = $(this).val();
492 calculateContainerWidth();
493 $targetRef.select2('destroy');
494 $('#target_ref_loading').show();
493 $targetRepo.on('change', function(e){
494 var repoName = $(this).val();
495 calculateContainerWidth();
496 $targetRef.select2('destroy');
497 $('#target_ref_loading').show();
495 498
496 $.ajax({
497 url: pyroutes.url('pullrequest_repo_refs',
498 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
499 data: {},
500 dataType: 'json',
501 type: 'GET',
502 success: function(data) {
503 $('#target_ref_loading').hide();
504 targetRepoChanged(data);
505 loadRepoRefDiffPreview();
506 },
507 error: function(data, textStatus, errorThrown) {
508 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
509 }
510 })
499 $.ajax({
500 url: pyroutes.url('pullrequest_repo_refs',
501 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
502 data: {},
503 dataType: 'json',
504 type: 'GET',
505 success: function(data) {
506 $('#target_ref_loading').hide();
507 targetRepoChanged(data);
508 loadRepoRefDiffPreview();
509 },
510 error: function(data, textStatus, errorThrown) {
511 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
512 }
513 })
511 514
512 });
513
514 $pullRequestForm.on('submit', function(e){
515 prButtonLock(true, null, 'all');
516 });
515 });
517 516
518 prButtonLock(true, "${_('Please select source and target')}", 'all');
517 $pullRequestForm.on('submit', function(e){
518 // Flush changes into textarea
519 codeMirrorInstance.save();
520 prButtonLock(true, null, 'all');
521 });
519 522
520 // auto-load on init, the target refs select2
521 calculateContainerWidth();
522 targetRepoChanged(defaultTargetRepoData);
523 prButtonLock(true, "${_('Please select source and target')}", 'all');
523 524
524 $('#pullrequest_title').on('keyup', function(e){
525 $(this).removeClass('autogenerated-title');
526 });
525 // auto-load on init, the target refs select2
526 calculateContainerWidth();
527 targetRepoChanged(defaultTargetRepoData);
527 528
528 % if c.default_source_ref:
529 // in case we have a pre-selected value, use it now
530 $sourceRef.select2('val', '${c.default_source_ref}');
531 // diff preview load
532 loadRepoRefDiffPreview();
533 // default reviewers
534 reviewersController.loadDefaultReviewers(
535 sourceRepo(), sourceRef(), targetRepo(), targetRef());
536 % endif
529 $('#pullrequest_title').on('keyup', function(e){
530 $(this).removeClass('autogenerated-title');
531 });
537 532
538 ReviewerAutoComplete('#user');
539 });
533 % if c.default_source_ref:
534 // in case we have a pre-selected value, use it now
535 $sourceRef.select2('val', '${c.default_source_ref}');
536 // diff preview load
537 loadRepoRefDiffPreview();
538 // default reviewers
539 reviewersController.loadDefaultReviewers(
540 sourceRepo(), sourceRef(), targetRepo(), targetRef());
541 % endif
542
543 ReviewerAutoComplete('#user');
544 });
540 545 </script>
541 546
542 547 </%def>
@@ -1,856 +1,857 b''
1 1 <%inherit file="/base/base.mako"/>
2 2 <%namespace name="base" file="/base/base.mako"/>
3 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3 4
4 5 <%def name="title()">
5 6 ${_('%s Pull Request #%s') % (c.repo_name, c.pull_request.pull_request_id)}
6 7 %if c.rhodecode_name:
7 8 &middot; ${h.branding(c.rhodecode_name)}
8 9 %endif
9 10 </%def>
10 11
11 12 <%def name="breadcrumbs_links()">
12 13 <span id="pr-title">
13 14 ${c.pull_request.title}
14 15 %if c.pull_request.is_closed():
15 16 (${_('Closed')})
16 17 %endif
17 18 </span>
18 19 <div id="pr-title-edit" class="input" style="display: none;">
19 20 ${h.text('pullrequest_title', id_="pr-title-input", class_="large", value=c.pull_request.title)}
20 21 </div>
21 22 </%def>
22 23
23 24 <%def name="menu_bar_nav()">
24 25 ${self.menu_items(active='repositories')}
25 26 </%def>
26 27
27 28 <%def name="menu_bar_subnav()">
28 29 ${self.repo_menu(active='showpullrequest')}
29 30 </%def>
30 31
31 32 <%def name="main()">
32 33
33 34 <script type="text/javascript">
34 35 // TODO: marcink switch this to pyroutes
35 36 AJAX_COMMENT_DELETE_URL = "${h.route_path('pullrequest_comment_delete',repo_name=c.repo_name,pull_request_id=c.pull_request.pull_request_id,comment_id='__COMMENT_ID__')}";
36 37 templateContext.pull_request_data.pull_request_id = ${c.pull_request.pull_request_id};
37 38 </script>
38 39 <div class="box">
39 40
40 41 <div class="title">
41 42 ${self.repo_page_title(c.rhodecode_db_repo)}
42 43 </div>
43 44
44 45 ${self.breadcrumbs()}
45 46
46 47 <div class="box pr-summary">
47 48
48 49 <div class="summary-details block-left">
49 50 <% summary = lambda n:{False:'summary-short'}.get(n) %>
50 51 <div class="pr-details-title">
51 52 <a href="${h.route_path('pull_requests_global', pull_request_id=c.pull_request.pull_request_id)}">${_('Pull request #%s') % c.pull_request.pull_request_id}</a> ${_('From')} ${h.format_date(c.pull_request.created_on)}
52 53 %if c.allowed_to_update:
53 54 <div id="delete_pullrequest" class="pull-right action_button ${'' if c.allowed_to_delete else 'disabled' }" style="clear:inherit;padding: 0">
54 55 % if c.allowed_to_delete:
55 56 ${h.secure_form(h.route_path('pullrequest_delete', repo_name=c.pull_request.target_repo.repo_name, pull_request_id=c.pull_request.pull_request_id), request=request)}
56 57 ${h.submit('remove_%s' % c.pull_request.pull_request_id, _('Delete'),
57 58 class_="btn btn-link btn-danger no-margin",onclick="return confirm('"+_('Confirm to delete this pull request')+"');")}
58 59 ${h.end_form()}
59 60 % else:
60 61 ${_('Delete')}
61 62 % endif
62 63 </div>
63 64 <div id="open_edit_pullrequest" class="pull-right action_button">${_('Edit')}</div>
64 65 <div id="close_edit_pullrequest" class="pull-right action_button" style="display: none;padding: 0">${_('Cancel')}</div>
65 66 %endif
66 67 </div>
67 68
68 69 <div id="summary" class="fields pr-details-content">
69 70 <div class="field">
70 71 <div class="label-summary">
71 72 <label>${_('Source')}:</label>
72 73 </div>
73 74 <div class="input">
74 75 <div class="pr-origininfo">
75 76 ## branch link is only valid if it is a branch
76 77 <span class="tag">
77 78 %if c.pull_request.source_ref_parts.type == 'branch':
78 79 <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.source_repo.repo_name, _query=dict(branch=c.pull_request.source_ref_parts.name))}">${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}</a>
79 80 %else:
80 81 ${c.pull_request.source_ref_parts.type}: ${c.pull_request.source_ref_parts.name}
81 82 %endif
82 83 </span>
83 84 <span class="clone-url">
84 85 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.source_repo.repo_name)}">${c.pull_request.source_repo.clone_url()}</a>
85 86 </span>
86 87 <br/>
87 88 % if c.ancestor_commit:
88 89 ${_('Common ancestor')}:
89 90 <code><a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=c.ancestor_commit.raw_id)}">${h.show_id(c.ancestor_commit)}</a></code>
90 91 % endif
91 92 </div>
92 93 %if h.is_hg(c.pull_request.source_repo):
93 94 <% clone_url = 'hg pull -r {} {}'.format(h.short_id(c.source_ref), c.pull_request.source_repo.clone_url()) %>
94 95 %elif h.is_git(c.pull_request.source_repo):
95 96 <% clone_url = 'git pull {} {}'.format(c.pull_request.source_repo.clone_url(), c.pull_request.source_ref_parts.name) %>
96 97 %endif
97 98
98 99 <div class="">
99 100 <input type="text" class="input-monospace pr-pullinfo" value="${clone_url}" readonly="readonly">
100 101 <i class="tooltip icon-clipboard clipboard-action pull-right pr-pullinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the pull url')}"></i>
101 102 </div>
102 103
103 104 </div>
104 105 </div>
105 106 <div class="field">
106 107 <div class="label-summary">
107 108 <label>${_('Target')}:</label>
108 109 </div>
109 110 <div class="input">
110 111 <div class="pr-targetinfo">
111 112 ## branch link is only valid if it is a branch
112 113 <span class="tag">
113 114 %if c.pull_request.target_ref_parts.type == 'branch':
114 115 <a href="${h.route_path('repo_changelog', repo_name=c.pull_request.target_repo.repo_name, _query=dict(branch=c.pull_request.target_ref_parts.name))}">${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}</a>
115 116 %else:
116 117 ${c.pull_request.target_ref_parts.type}: ${c.pull_request.target_ref_parts.name}
117 118 %endif
118 119 </span>
119 120 <span class="clone-url">
120 121 <a href="${h.route_path('repo_summary', repo_name=c.pull_request.target_repo.repo_name)}">${c.pull_request.target_repo.clone_url()}</a>
121 122 </span>
122 123 </div>
123 124 </div>
124 125 </div>
125 126
126 127 ## Link to the shadow repository.
127 128 <div class="field">
128 129 <div class="label-summary">
129 130 <label>${_('Merge')}:</label>
130 131 </div>
131 132 <div class="input">
132 133 % if not c.pull_request.is_closed() and c.pull_request.shadow_merge_ref:
133 134 %if h.is_hg(c.pull_request.target_repo):
134 135 <% clone_url = 'hg clone --update {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
135 136 %elif h.is_git(c.pull_request.target_repo):
136 137 <% clone_url = 'git clone --branch {} {} pull-request-{}'.format(c.pull_request.shadow_merge_ref.name, c.shadow_clone_url, c.pull_request.pull_request_id) %>
137 138 %endif
138 139 <div class="">
139 140 <input type="text" class="input-monospace pr-mergeinfo" value="${clone_url}" readonly="readonly">
140 141 <i class="tooltip icon-clipboard clipboard-action pull-right pr-mergeinfo-copy" data-clipboard-text="${clone_url}" title="${_('Copy the clone url')}"></i>
141 142 </div>
142 143 % else:
143 144 <div class="">
144 145 ${_('Shadow repository data not available')}.
145 146 </div>
146 147 % endif
147 148 </div>
148 149 </div>
149 150
150 151 <div class="field">
151 152 <div class="label-summary">
152 153 <label>${_('Review')}:</label>
153 154 </div>
154 155 <div class="input">
155 156 %if c.pull_request_review_status:
156 157 <div class="${'flag_status %s' % c.pull_request_review_status} tooltip pull-left"></div>
157 158 <span class="changeset-status-lbl tooltip">
158 159 %if c.pull_request.is_closed():
159 160 ${_('Closed')},
160 161 %endif
161 162 ${h.commit_status_lbl(c.pull_request_review_status)}
162 163 </span>
163 164 - ${_ungettext('calculated based on %s reviewer vote', 'calculated based on %s reviewers votes', len(c.pull_request_reviewers)) % len(c.pull_request_reviewers)}
164 165 %endif
165 166 </div>
166 167 </div>
167 168 <div class="field">
168 169 <div class="pr-description-label label-summary">
169 170 <label>${_('Description')}:</label>
170 171 </div>
171 172 <div id="pr-desc" class="input">
172 <div class="pr-description">${h.urlify_commit_message(c.pull_request.description, c.repo_name)}</div>
173 <div class="pr-description">${h.render(c.pull_request.description, renderer=c.visual.default_renderer)}</div>
173 174 </div>
174 175 <div id="pr-desc-edit" class="input textarea editor" style="display: none;">
175 <textarea id="pr-description-input" size="30">${c.pull_request.description}</textarea>
176 ${dt.markup_form('pr-description-input', form_text=c.pull_request.description)}
176 177 </div>
177 178 </div>
178 179
179 180 <div class="field">
180 181 <div class="label-summary">
181 182 <label>${_('Versions')}:</label>
182 183 </div>
183 184
184 185 <% outdated_comm_count_ver = len(c.inline_versions[None]['outdated']) %>
185 186 <% general_outdated_comm_count_ver = len(c.comment_versions[None]['outdated']) %>
186 187
187 188 <div class="pr-versions">
188 189 % if c.show_version_changes:
189 190 <% outdated_comm_count_ver = len(c.inline_versions[c.at_version_num]['outdated']) %>
190 191 <% general_outdated_comm_count_ver = len(c.comment_versions[c.at_version_num]['outdated']) %>
191 192 <a id="show-pr-versions" class="input" onclick="return versionController.toggleVersionView(this)" href="#show-pr-versions"
192 193 data-toggle-on="${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}"
193 194 data-toggle-off="${_('Hide all versions of this pull request')}">
194 195 ${_ungettext('{} version available for this pull request, show it.', '{} versions available for this pull request, show them.', len(c.versions)).format(len(c.versions))}
195 196 </a>
196 197 <table>
197 198 ## SHOW ALL VERSIONS OF PR
198 199 <% ver_pr = None %>
199 200
200 201 % for data in reversed(list(enumerate(c.versions, 1))):
201 202 <% ver_pos = data[0] %>
202 203 <% ver = data[1] %>
203 204 <% ver_pr = ver.pull_request_version_id %>
204 205 <% display_row = '' if c.at_version and (c.at_version_num == ver_pr or c.from_version_num == ver_pr) else 'none' %>
205 206
206 207 <tr class="version-pr" style="display: ${display_row}">
207 208 <td>
208 209 <code>
209 210 <a href="${request.current_route_path(_query=dict(version=ver_pr or 'latest'))}">v${ver_pos}</a>
210 211 </code>
211 212 </td>
212 213 <td>
213 214 <input ${'checked="checked"' if c.from_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_source" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
214 215 <input ${'checked="checked"' if c.at_version_num == ver_pr else ''} class="compare-radio-button" type="radio" name="ver_target" value="${ver_pr or 'latest'}" data-ver-pos="${ver_pos}"/>
215 216 </td>
216 217 <td>
217 218 <% review_status = c.review_versions[ver_pr].status if ver_pr in c.review_versions else 'not_reviewed' %>
218 219 <div class="${'flag_status %s' % review_status} tooltip pull-left" title="${_('Your review status at this version')}">
219 220 </div>
220 221 </td>
221 222 <td>
222 223 % if c.at_version_num != ver_pr:
223 224 <i class="icon-comment"></i>
224 225 <code class="tooltip" title="${_('Comment from pull request version v{0}, general:{1} inline:{2}').format(ver_pos, len(c.comment_versions[ver_pr]['at']), len(c.inline_versions[ver_pr]['at']))}">
225 226 G:${len(c.comment_versions[ver_pr]['at'])} / I:${len(c.inline_versions[ver_pr]['at'])}
226 227 </code>
227 228 % endif
228 229 </td>
229 230 <td>
230 231 ##<code>${ver.source_ref_parts.commit_id[:6]}</code>
231 232 </td>
232 233 <td>
233 234 ${h.age_component(ver.updated_on, time_is_local=True)}
234 235 </td>
235 236 </tr>
236 237 % endfor
237 238
238 239 <tr>
239 240 <td colspan="6">
240 241 <button id="show-version-diff" onclick="return versionController.showVersionDiff()" class="btn btn-sm" style="display: none"
241 242 data-label-text-locked="${_('select versions to show changes')}"
242 243 data-label-text-diff="${_('show changes between versions')}"
243 244 data-label-text-show="${_('show pull request for this version')}"
244 245 >
245 246 ${_('select versions to show changes')}
246 247 </button>
247 248 </td>
248 249 </tr>
249 250
250 251 ## show comment/inline comments summary
251 252 <%def name="comments_summary()">
252 253 <tr>
253 254 <td colspan="6" class="comments-summary-td">
254 255
255 256 % if c.at_version:
256 257 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['display']) %>
257 258 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['display']) %>
258 259 ${_('Comments at this version')}:
259 260 % else:
260 261 <% inline_comm_count_ver = len(c.inline_versions[c.at_version_num]['until']) %>
261 262 <% general_comm_count_ver = len(c.comment_versions[c.at_version_num]['until']) %>
262 263 ${_('Comments for this pull request')}:
263 264 % endif
264 265
265 266
266 267 %if general_comm_count_ver:
267 268 <a href="#comments">${_("%d General ") % general_comm_count_ver}</a>
268 269 %else:
269 270 ${_("%d General ") % general_comm_count_ver}
270 271 %endif
271 272
272 273 %if inline_comm_count_ver:
273 274 , <a href="#" onclick="return Rhodecode.comments.nextComment();" id="inline-comments-counter">${_("%d Inline") % inline_comm_count_ver}</a>
274 275 %else:
275 276 , ${_("%d Inline") % inline_comm_count_ver}
276 277 %endif
277 278
278 279 %if outdated_comm_count_ver:
279 280 , <a href="#" onclick="showOutdated(); Rhodecode.comments.nextOutdatedComment(); return false;">${_("%d Outdated") % outdated_comm_count_ver}</a>
280 281 <a href="#" class="showOutdatedComments" onclick="showOutdated(this); return false;"> | ${_('show outdated comments')}</a>
281 282 <a href="#" class="hideOutdatedComments" style="display: none" onclick="hideOutdated(this); return false;"> | ${_('hide outdated comments')}</a>
282 283 %else:
283 284 , ${_("%d Outdated") % outdated_comm_count_ver}
284 285 %endif
285 286 </td>
286 287 </tr>
287 288 </%def>
288 289 ${comments_summary()}
289 290 </table>
290 291 % else:
291 292 <div class="input">
292 293 ${_('Pull request versions not available')}.
293 294 </div>
294 295 <div>
295 296 <table>
296 297 ${comments_summary()}
297 298 </table>
298 299 </div>
299 300 % endif
300 301 </div>
301 302 </div>
302 303
303 304 <div id="pr-save" class="field" style="display: none;">
304 305 <div class="label-summary"></div>
305 306 <div class="input">
306 307 <span id="edit_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</span>
307 308 </div>
308 309 </div>
309 310 </div>
310 311 </div>
311 312 <div>
312 313 ## AUTHOR
313 314 <div class="reviewers-title block-right">
314 315 <div class="pr-details-title">
315 316 ${_('Author of this pull request')}
316 317 </div>
317 318 </div>
318 319 <div class="block-right pr-details-content reviewers">
319 320 <ul class="group_members">
320 321 <li>
321 322 ${self.gravatar_with_user(c.pull_request.author.email, 16)}
322 323 </li>
323 324 </ul>
324 325 </div>
325 326
326 327 ## REVIEW RULES
327 328 <div id="review_rules" style="display: none" class="reviewers-title block-right">
328 329 <div class="pr-details-title">
329 330 ${_('Reviewer rules')}
330 331 %if c.allowed_to_update:
331 332 <span id="close_edit_reviewers" class="block-right action_button last-item" style="display: none;">${_('Close')}</span>
332 333 %endif
333 334 </div>
334 335 <div class="pr-reviewer-rules">
335 336 ## review rules will be appended here, by default reviewers logic
336 337 </div>
337 338 <input id="review_data" type="hidden" name="review_data" value="">
338 339 </div>
339 340
340 341 ## REVIEWERS
341 342 <div class="reviewers-title block-right">
342 343 <div class="pr-details-title">
343 344 ${_('Pull request reviewers')}
344 345 %if c.allowed_to_update:
345 346 <span id="open_edit_reviewers" class="block-right action_button last-item">${_('Edit')}</span>
346 347 %endif
347 348 </div>
348 349 </div>
349 350 <div id="reviewers" class="block-right pr-details-content reviewers">
350 351
351 352 ## members redering block
352 353 <input type="hidden" name="__start__" value="review_members:sequence">
353 354 <ul id="review_members" class="group_members">
354 355
355 356 % for review_obj, member, reasons, mandatory, status in c.pull_request_reviewers:
356 357 <script>
357 358 var member = ${h.json.dumps(h.reviewer_as_json(member, reasons=reasons, mandatory=mandatory, user_group=review_obj.rule_user_group_data()))|n};
358 359 var status = "${(status[0][1].status if status else 'not_reviewed')}";
359 360 var status_lbl = "${h.commit_status_lbl(status[0][1].status if status else 'not_reviewed')}";
360 361 var allowed_to_update = ${h.json.dumps(c.allowed_to_update)};
361 362
362 363 var entry = renderTemplate('reviewMemberEntry', {
363 364 'member': member,
364 365 'mandatory': member.mandatory,
365 366 'reasons': member.reasons,
366 367 'allowed_to_update': allowed_to_update,
367 368 'review_status': status,
368 369 'review_status_label': status_lbl,
369 370 'user_group': member.user_group,
370 371 'create': false
371 372 });
372 373 $('#review_members').append(entry)
373 374 </script>
374 375
375 376 % endfor
376 377
377 378 </ul>
378 379 <input type="hidden" name="__end__" value="review_members:sequence">
379 380 ## end members redering block
380 381
381 382 %if not c.pull_request.is_closed():
382 383 <div id="add_reviewer" class="ac" style="display: none;">
383 384 %if c.allowed_to_update:
384 385 % if not c.forbid_adding_reviewers:
385 386 <div id="add_reviewer_input" class="reviewer_ac">
386 387 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
387 388 <div id="reviewers_container"></div>
388 389 </div>
389 390 % endif
390 391 <div class="pull-right">
391 392 <button id="update_pull_request" class="btn btn-small no-margin">${_('Save Changes')}</button>
392 393 </div>
393 394 %endif
394 395 </div>
395 396 %endif
396 397 </div>
397 398 </div>
398 399 </div>
399 400 <div class="box">
400 401 ##DIFF
401 402 <div class="table" >
402 403 <div id="changeset_compare_view_content">
403 404 ##CS
404 405 % if c.missing_requirements:
405 406 <div class="box">
406 407 <div class="alert alert-warning">
407 408 <div>
408 409 <strong>${_('Missing requirements:')}</strong>
409 410 ${_('These commits cannot be displayed, because this repository uses the Mercurial largefiles extension, which was not enabled.')}
410 411 </div>
411 412 </div>
412 413 </div>
413 414 % elif c.missing_commits:
414 415 <div class="box">
415 416 <div class="alert alert-warning">
416 417 <div>
417 418 <strong>${_('Missing commits')}:</strong>
418 419 ${_('This pull request cannot be displayed, because one or more commits no longer exist in the source repository.')}
419 420 ${_('Please update this pull request, push the commits back into the source repository, or consider closing this pull request.')}
420 421 ${_('Consider doing a {force_refresh_url} in case you think this is an error.').format(force_refresh_url=h.link_to('force refresh', h.current_route_path(request, force_refresh='1')))|n}
421 422 </div>
422 423 </div>
423 424 </div>
424 425 % endif
425 426
426 427 <div class="compare_view_commits_title">
427 428 % if not c.compare_mode:
428 429
429 430 % if c.at_version_pos:
430 431 <h4>
431 432 ${_('Showing changes at v%d, commenting is disabled.') % c.at_version_pos}
432 433 </h4>
433 434 % endif
434 435
435 436 <div class="pull-left">
436 437 <div class="btn-group">
437 438 <a
438 439 class="btn"
439 440 href="#"
440 441 onclick="$('.compare_select').show();$('.compare_select_hidden').hide(); return false">
441 442 ${_ungettext('Expand %s commit','Expand %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
442 443 </a>
443 444 <a
444 445 class="btn"
445 446 href="#"
446 447 onclick="$('.compare_select').hide();$('.compare_select_hidden').show(); return false">
447 448 ${_ungettext('Collapse %s commit','Collapse %s commits', len(c.commit_ranges)) % len(c.commit_ranges)}
448 449 </a>
449 450 </div>
450 451 </div>
451 452
452 453 <div class="pull-right">
453 454 % if c.allowed_to_update and not c.pull_request.is_closed():
454 455 <a id="update_commits" class="btn btn-primary no-margin pull-right">${_('Update commits')}</a>
455 456 % else:
456 457 <a class="tooltip btn disabled pull-right" disabled="disabled" title="${_('Update is disabled for current view')}">${_('Update commits')}</a>
457 458 % endif
458 459
459 460 </div>
460 461 % endif
461 462 </div>
462 463
463 464 % if not c.missing_commits:
464 465 % if c.compare_mode:
465 466 % if c.at_version:
466 467 <h4>
467 468 ${_('Commits and changes between v{ver_from} and {ver_to} of this pull request, commenting is disabled').format(ver_from=c.from_version_pos, ver_to=c.at_version_pos if c.at_version_pos else 'latest')}:
468 469 </h4>
469 470
470 471 <div class="subtitle-compare">
471 472 ${_('commits added: {}, removed: {}').format(len(c.commit_changes_summary.added), len(c.commit_changes_summary.removed))}
472 473 </div>
473 474
474 475 <div class="container">
475 476 <table class="rctable compare_view_commits">
476 477 <tr>
477 478 <th></th>
478 479 <th>${_('Time')}</th>
479 480 <th>${_('Author')}</th>
480 481 <th>${_('Commit')}</th>
481 482 <th></th>
482 483 <th>${_('Description')}</th>
483 484 </tr>
484 485
485 486 % for c_type, commit in c.commit_changes:
486 487 % if c_type in ['a', 'r']:
487 488 <%
488 489 if c_type == 'a':
489 490 cc_title = _('Commit added in displayed changes')
490 491 elif c_type == 'r':
491 492 cc_title = _('Commit removed in displayed changes')
492 493 else:
493 494 cc_title = ''
494 495 %>
495 496 <tr id="row-${commit.raw_id}" commit_id="${commit.raw_id}" class="compare_select">
496 497 <td>
497 498 <div class="commit-change-indicator color-${c_type}-border">
498 499 <div class="commit-change-content color-${c_type} tooltip" title="${h.tooltip(cc_title)}">
499 500 ${c_type.upper()}
500 501 </div>
501 502 </div>
502 503 </td>
503 504 <td class="td-time">
504 505 ${h.age_component(commit.date)}
505 506 </td>
506 507 <td class="td-user">
507 508 ${base.gravatar_with_user(commit.author, 16)}
508 509 </td>
509 510 <td class="td-hash">
510 511 <code>
511 512 <a href="${h.route_path('repo_commit', repo_name=c.target_repo.repo_name, commit_id=commit.raw_id)}">
512 513 r${commit.revision}:${h.short_id(commit.raw_id)}
513 514 </a>
514 515 ${h.hidden('revisions', commit.raw_id)}
515 516 </code>
516 517 </td>
517 518 <td class="expand_commit" data-commit-id="${commit.raw_id}" title="${_( 'Expand commit message')}">
518 519 <div class="show_more_col">
519 520 <i class="show_more"></i>
520 521 </div>
521 522 </td>
522 523 <td class="mid td-description">
523 524 <div class="log-container truncate-wrap">
524 525 <div class="message truncate" id="c-${commit.raw_id}" data-message-raw="${commit.message}">
525 526 ${h.urlify_commit_message(commit.message, c.repo_name)}
526 527 </div>
527 528 </div>
528 529 </td>
529 530 </tr>
530 531 % endif
531 532 % endfor
532 533 </table>
533 534 </div>
534 535
535 536 <script>
536 537 $('.expand_commit').on('click',function(e){
537 538 var target_expand = $(this);
538 539 var cid = target_expand.data('commitId');
539 540
540 541 if (target_expand.hasClass('open')){
541 542 $('#c-'+cid).css({
542 543 'height': '1.5em',
543 544 'white-space': 'nowrap',
544 545 'text-overflow': 'ellipsis',
545 546 'overflow':'hidden'
546 547 });
547 548 target_expand.removeClass('open');
548 549 }
549 550 else {
550 551 $('#c-'+cid).css({
551 552 'height': 'auto',
552 553 'white-space': 'pre-line',
553 554 'text-overflow': 'initial',
554 555 'overflow':'visible'
555 556 });
556 557 target_expand.addClass('open');
557 558 }
558 559 });
559 560 </script>
560 561
561 562 % endif
562 563
563 564 % else:
564 565 <%include file="/compare/compare_commits.mako" />
565 566 % endif
566 567
567 568 <div class="cs_files">
568 569 <%namespace name="cbdiffs" file="/codeblocks/diffs.mako"/>
569 570 ${cbdiffs.render_diffset_menu()}
570 571 ${cbdiffs.render_diffset(
571 572 c.diffset, use_comments=True,
572 573 collapse_when_files_over=30,
573 574 disable_new_comments=not c.allowed_to_comment,
574 575 deleted_files_comments=c.deleted_files_comments,
575 576 inline_comments=c.inline_comments)}
576 577 </div>
577 578 % else:
578 579 ## skipping commits we need to clear the view for missing commits
579 580 <div style="clear:both;"></div>
580 581 % endif
581 582
582 583 </div>
583 584 </div>
584 585
585 586 ## template for inline comment form
586 587 <%namespace name="comment" file="/changeset/changeset_file_comment.mako"/>
587 588
588 589 ## render general comments
589 590
590 591 <div id="comment-tr-show">
591 592 <div class="comment">
592 593 % if general_outdated_comm_count_ver:
593 594 <div class="meta">
594 595 % if general_outdated_comm_count_ver == 1:
595 596 ${_('there is {num} general comment from older versions').format(num=general_outdated_comm_count_ver)},
596 597 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show it')}</a>
597 598 % else:
598 599 ${_('there are {num} general comments from older versions').format(num=general_outdated_comm_count_ver)},
599 600 <a href="#show-hidden-comments" onclick="$('.comment-general.comment-outdated').show(); $(this).parent().hide(); return false;">${_('show them')}</a>
600 601 % endif
601 602 </div>
602 603 % endif
603 604 </div>
604 605 </div>
605 606
606 607 ${comment.generate_comments(c.comments, include_pull_request=True, is_pull_request=True)}
607 608
608 609 % if not c.pull_request.is_closed():
609 610 ## merge status, and merge action
610 611 <div class="pull-request-merge">
611 612 <%include file="/pullrequests/pullrequest_merge_checks.mako"/>
612 613 </div>
613 614
614 615 ## main comment form and it status
615 616 ${comment.comments(h.route_path('pullrequest_comment_create', repo_name=c.repo_name,
616 617 pull_request_id=c.pull_request.pull_request_id),
617 618 c.pull_request_review_status,
618 619 is_pull_request=True, change_status=c.allowed_to_change_status)}
619 620 %endif
620 621
621 622 <script type="text/javascript">
622 623 if (location.hash) {
623 624 var result = splitDelimitedHash(location.hash);
624 625 var line = $('html').find(result.loc);
625 626 // show hidden comments if we use location.hash
626 627 if (line.hasClass('comment-general')) {
627 628 $(line).show();
628 629 } else if (line.hasClass('comment-inline')) {
629 630 $(line).show();
630 631 var $cb = $(line).closest('.cb');
631 632 $cb.removeClass('cb-collapsed')
632 633 }
633 634 if (line.length > 0){
634 635 offsetScroll(line, 70);
635 636 }
636 637 }
637 638
638 639 versionController = new VersionController();
639 640 versionController.init();
640 641
641 642 reviewersController = new ReviewersController();
642 643
643 644 $(function(){
644 645
645 646 // custom code mirror
646 var codeMirrorInstance = initPullRequestsCodeMirror('#pr-description-input');
647 var codeMirrorInstance = $('#pr-description-input').get(0).MarkupForm.cm;
647 648
648 649 var PRDetails = {
649 650 editButton: $('#open_edit_pullrequest'),
650 651 closeButton: $('#close_edit_pullrequest'),
651 652 deleteButton: $('#delete_pullrequest'),
652 653 viewFields: $('#pr-desc, #pr-title'),
653 654 editFields: $('#pr-desc-edit, #pr-title-edit, #pr-save'),
654 655
655 656 init: function() {
656 657 var that = this;
657 658 this.editButton.on('click', function(e) { that.edit(); });
658 659 this.closeButton.on('click', function(e) { that.view(); });
659 660 },
660 661
661 662 edit: function(event) {
662 663 this.viewFields.hide();
663 664 this.editButton.hide();
664 665 this.deleteButton.hide();
665 666 this.closeButton.show();
666 667 this.editFields.show();
667 668 codeMirrorInstance.refresh();
668 669 },
669 670
670 671 view: function(event) {
671 672 this.editButton.show();
672 673 this.deleteButton.show();
673 674 this.editFields.hide();
674 675 this.closeButton.hide();
675 676 this.viewFields.show();
676 677 }
677 678 };
678 679
679 680 var ReviewersPanel = {
680 681 editButton: $('#open_edit_reviewers'),
681 682 closeButton: $('#close_edit_reviewers'),
682 683 addButton: $('#add_reviewer'),
683 684 removeButtons: $('.reviewer_member_remove,.reviewer_member_mandatory_remove'),
684 685
685 686 init: function() {
686 687 var self = this;
687 688 this.editButton.on('click', function(e) { self.edit(); });
688 689 this.closeButton.on('click', function(e) { self.close(); });
689 690 },
690 691
691 692 edit: function(event) {
692 693 this.editButton.hide();
693 694 this.closeButton.show();
694 695 this.addButton.show();
695 696 this.removeButtons.css('visibility', 'visible');
696 697 // review rules
697 698 reviewersController.loadReviewRules(
698 699 ${c.pull_request.reviewer_data_json | n});
699 700 },
700 701
701 702 close: function(event) {
702 703 this.editButton.show();
703 704 this.closeButton.hide();
704 705 this.addButton.hide();
705 706 this.removeButtons.css('visibility', 'hidden');
706 707 // hide review rules
707 708 reviewersController.hideReviewRules()
708 709 }
709 710 };
710 711
711 712 PRDetails.init();
712 713 ReviewersPanel.init();
713 714
714 715 showOutdated = function(self){
715 716 $('.comment-inline.comment-outdated').show();
716 717 $('.filediff-outdated').show();
717 718 $('.showOutdatedComments').hide();
718 719 $('.hideOutdatedComments').show();
719 720 };
720 721
721 722 hideOutdated = function(self){
722 723 $('.comment-inline.comment-outdated').hide();
723 724 $('.filediff-outdated').hide();
724 725 $('.hideOutdatedComments').hide();
725 726 $('.showOutdatedComments').show();
726 727 };
727 728
728 729 refreshMergeChecks = function(){
729 730 var loadUrl = "${request.current_route_path(_query=dict(merge_checks=1))}";
730 731 $('.pull-request-merge').css('opacity', 0.3);
731 732 $('.action-buttons-extra').css('opacity', 0.3);
732 733
733 734 $('.pull-request-merge').load(
734 735 loadUrl, function() {
735 736 $('.pull-request-merge').css('opacity', 1);
736 737
737 738 $('.action-buttons-extra').css('opacity', 1);
738 739 injectCloseAction();
739 740 }
740 741 );
741 742 };
742 743
743 744 injectCloseAction = function() {
744 745 var closeAction = $('#close-pull-request-action').html();
745 746 var $actionButtons = $('.action-buttons-extra');
746 747 // clear the action before
747 748 $actionButtons.html("");
748 749 $actionButtons.html(closeAction);
749 750 };
750 751
751 752 closePullRequest = function (status) {
752 753 // inject closing flag
753 754 $('.action-buttons-extra').append('<input type="hidden" class="close-pr-input" id="close_pull_request" value="1">');
754 755 $(generalCommentForm.statusChange).select2("val", status).trigger('change');
755 756 $(generalCommentForm.submitForm).submit();
756 757 };
757 758
758 759 $('#show-outdated-comments').on('click', function(e){
759 760 var button = $(this);
760 761 var outdated = $('.comment-outdated');
761 762
762 763 if (button.html() === "(Show)") {
763 764 button.html("(Hide)");
764 765 outdated.show();
765 766 } else {
766 767 button.html("(Show)");
767 768 outdated.hide();
768 769 }
769 770 });
770 771
771 772 $('.show-inline-comments').on('change', function(e){
772 773 var show = 'none';
773 774 var target = e.currentTarget;
774 775 if(target.checked){
775 776 show = ''
776 777 }
777 778 var boxid = $(target).attr('id_for');
778 779 var comments = $('#{0} .inline-comments'.format(boxid));
779 780 var fn_display = function(idx){
780 781 $(this).css('display', show);
781 782 };
782 783 $(comments).each(fn_display);
783 784 var btns = $('#{0} .inline-comments-button'.format(boxid));
784 785 $(btns).each(fn_display);
785 786 });
786 787
787 788 $('#merge_pull_request_form').submit(function() {
788 789 if (!$('#merge_pull_request').attr('disabled')) {
789 790 $('#merge_pull_request').attr('disabled', 'disabled');
790 791 }
791 792 return true;
792 793 });
793 794
794 795 $('#edit_pull_request').on('click', function(e){
795 796 var title = $('#pr-title-input').val();
796 797 var description = codeMirrorInstance.getValue();
797 798 editPullRequest(
798 799 "${c.repo_name}", "${c.pull_request.pull_request_id}",
799 800 title, description);
800 801 });
801 802
802 803 $('#update_pull_request').on('click', function(e){
803 804 $(this).attr('disabled', 'disabled');
804 805 $(this).addClass('disabled');
805 806 $(this).html(_gettext('Saving...'));
806 807 reviewersController.updateReviewers(
807 808 "${c.repo_name}", "${c.pull_request.pull_request_id}");
808 809 });
809 810
810 811 $('#update_commits').on('click', function(e){
811 812 var isDisabled = !$(e.currentTarget).attr('disabled');
812 813 $(e.currentTarget).attr('disabled', 'disabled');
813 814 $(e.currentTarget).addClass('disabled');
814 815 $(e.currentTarget).removeClass('btn-primary');
815 816 $(e.currentTarget).text(_gettext('Updating...'));
816 817 if(isDisabled){
817 818 updateCommits(
818 819 "${c.repo_name}", "${c.pull_request.pull_request_id}");
819 820 }
820 821 });
821 822 // fixing issue with caches on firefox
822 823 $('#update_commits').removeAttr("disabled");
823 824
824 825 $('.show-inline-comments').on('click', function(e){
825 826 var boxid = $(this).attr('data-comment-id');
826 827 var button = $(this);
827 828
828 829 if(button.hasClass("comments-visible")) {
829 830 $('#{0} .inline-comments'.format(boxid)).each(function(index){
830 831 $(this).hide();
831 832 });
832 833 button.removeClass("comments-visible");
833 834 } else {
834 835 $('#{0} .inline-comments'.format(boxid)).each(function(index){
835 836 $(this).show();
836 837 });
837 838 button.addClass("comments-visible");
838 839 }
839 840 });
840 841
841 842 // register submit callback on commentForm form to track TODOs
842 843 window.commentFormGlobalSubmitSuccessCallback = function(){
843 844 refreshMergeChecks();
844 845 };
845 846 // initial injection
846 847 injectCloseAction();
847 848
848 849 ReviewerAutoComplete('#user');
849 850
850 851 })
851 852 </script>
852 853
853 854 </div>
854 855 </div>
855 856
856 857 </%def>
General Comments 0
You need to be logged in to leave comments. Login now