##// END OF EJS Templates
search: new UI for search, and repo group context search...
dan -
r3442:3bc8f801 default
parent child Browse files
Show More
@@ -1,687 +1,694 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import time
21 import time
22 import logging
22 import logging
23 import operator
23 import operator
24
24
25 from pyramid import compat
25 from pyramid import compat
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
26 from pyramid.httpexceptions import HTTPFound, HTTPForbidden, HTTPBadRequest
27
27
28 from rhodecode.lib import helpers as h, diffs
28 from rhodecode.lib import helpers as h, diffs
29 from rhodecode.lib.utils2 import (
29 from rhodecode.lib.utils2 import (
30 StrictAttributeDict, safe_int, datetime_to_time, safe_unicode)
30 StrictAttributeDict, safe_int, datetime_to_time, safe_unicode)
31 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
31 from rhodecode.lib.vcs.exceptions import RepositoryRequirementError
32 from rhodecode.model import repo
32 from rhodecode.model import repo
33 from rhodecode.model import repo_group
33 from rhodecode.model import repo_group
34 from rhodecode.model import user_group
34 from rhodecode.model import user_group
35 from rhodecode.model import user
35 from rhodecode.model import user
36 from rhodecode.model.db import User
36 from rhodecode.model.db import User
37 from rhodecode.model.scm import ScmModel
37 from rhodecode.model.scm import ScmModel
38 from rhodecode.model.settings import VcsSettingsModel
38 from rhodecode.model.settings import VcsSettingsModel
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 ADMIN_PREFIX = '/_admin'
43 ADMIN_PREFIX = '/_admin'
44 STATIC_FILE_PREFIX = '/_static'
44 STATIC_FILE_PREFIX = '/_static'
45
45
46 URL_NAME_REQUIREMENTS = {
46 URL_NAME_REQUIREMENTS = {
47 # group name can have a slash in them, but they must not end with a slash
47 # group name can have a slash in them, but they must not end with a slash
48 'group_name': r'.*?[^/]',
48 'group_name': r'.*?[^/]',
49 'repo_group_name': r'.*?[^/]',
49 'repo_group_name': r'.*?[^/]',
50 # repo names can have a slash in them, but they must not end with a slash
50 # repo names can have a slash in them, but they must not end with a slash
51 'repo_name': r'.*?[^/]',
51 'repo_name': r'.*?[^/]',
52 # file path eats up everything at the end
52 # file path eats up everything at the end
53 'f_path': r'.*',
53 'f_path': r'.*',
54 # reference types
54 # reference types
55 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
55 'source_ref_type': '(branch|book|tag|rev|\%\(source_ref_type\)s)',
56 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
56 'target_ref_type': '(branch|book|tag|rev|\%\(target_ref_type\)s)',
57 }
57 }
58
58
59
59
60 def add_route_with_slash(config,name, pattern, **kw):
60 def add_route_with_slash(config,name, pattern, **kw):
61 config.add_route(name, pattern, **kw)
61 config.add_route(name, pattern, **kw)
62 if not pattern.endswith('/'):
62 if not pattern.endswith('/'):
63 config.add_route(name + '_slash', pattern + '/', **kw)
63 config.add_route(name + '_slash', pattern + '/', **kw)
64
64
65
65
66 def add_route_requirements(route_path, requirements=None):
66 def add_route_requirements(route_path, requirements=None):
67 """
67 """
68 Adds regex requirements to pyramid routes using a mapping dict
68 Adds regex requirements to pyramid routes using a mapping dict
69 e.g::
69 e.g::
70 add_route_requirements('{repo_name}/settings')
70 add_route_requirements('{repo_name}/settings')
71 """
71 """
72 requirements = requirements or URL_NAME_REQUIREMENTS
72 requirements = requirements or URL_NAME_REQUIREMENTS
73 for key, regex in requirements.items():
73 for key, regex in requirements.items():
74 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
74 route_path = route_path.replace('{%s}' % key, '{%s:%s}' % (key, regex))
75 return route_path
75 return route_path
76
76
77
77
78 def get_format_ref_id(repo):
78 def get_format_ref_id(repo):
79 """Returns a `repo` specific reference formatter function"""
79 """Returns a `repo` specific reference formatter function"""
80 if h.is_svn(repo):
80 if h.is_svn(repo):
81 return _format_ref_id_svn
81 return _format_ref_id_svn
82 else:
82 else:
83 return _format_ref_id
83 return _format_ref_id
84
84
85
85
86 def _format_ref_id(name, raw_id):
86 def _format_ref_id(name, raw_id):
87 """Default formatting of a given reference `name`"""
87 """Default formatting of a given reference `name`"""
88 return name
88 return name
89
89
90
90
91 def _format_ref_id_svn(name, raw_id):
91 def _format_ref_id_svn(name, raw_id):
92 """Special way of formatting a reference for Subversion including path"""
92 """Special way of formatting a reference for Subversion including path"""
93 return '%s@%s' % (name, raw_id)
93 return '%s@%s' % (name, raw_id)
94
94
95
95
96 class TemplateArgs(StrictAttributeDict):
96 class TemplateArgs(StrictAttributeDict):
97 pass
97 pass
98
98
99
99
100 class BaseAppView(object):
100 class BaseAppView(object):
101
101
102 def __init__(self, context, request):
102 def __init__(self, context, request):
103 self.request = request
103 self.request = request
104 self.context = context
104 self.context = context
105 self.session = request.session
105 self.session = request.session
106 if not hasattr(request, 'user'):
106 if not hasattr(request, 'user'):
107 # NOTE(marcink): edge case, we ended up in matched route
107 # NOTE(marcink): edge case, we ended up in matched route
108 # but probably of web-app context, e.g API CALL/VCS CALL
108 # but probably of web-app context, e.g API CALL/VCS CALL
109 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
109 if hasattr(request, 'vcs_call') or hasattr(request, 'rpc_method'):
110 log.warning('Unable to process request `%s` in this scope', request)
110 log.warning('Unable to process request `%s` in this scope', request)
111 raise HTTPBadRequest()
111 raise HTTPBadRequest()
112
112
113 self._rhodecode_user = request.user # auth user
113 self._rhodecode_user = request.user # auth user
114 self._rhodecode_db_user = self._rhodecode_user.get_instance()
114 self._rhodecode_db_user = self._rhodecode_user.get_instance()
115 self._maybe_needs_password_change(
115 self._maybe_needs_password_change(
116 request.matched_route.name, self._rhodecode_db_user)
116 request.matched_route.name, self._rhodecode_db_user)
117
117
118 def _maybe_needs_password_change(self, view_name, user_obj):
118 def _maybe_needs_password_change(self, view_name, user_obj):
119 log.debug('Checking if user %s needs password change on view %s',
119 log.debug('Checking if user %s needs password change on view %s',
120 user_obj, view_name)
120 user_obj, view_name)
121 skip_user_views = [
121 skip_user_views = [
122 'logout', 'login',
122 'logout', 'login',
123 'my_account_password', 'my_account_password_update'
123 'my_account_password', 'my_account_password_update'
124 ]
124 ]
125
125
126 if not user_obj:
126 if not user_obj:
127 return
127 return
128
128
129 if user_obj.username == User.DEFAULT_USER:
129 if user_obj.username == User.DEFAULT_USER:
130 return
130 return
131
131
132 now = time.time()
132 now = time.time()
133 should_change = user_obj.user_data.get('force_password_change')
133 should_change = user_obj.user_data.get('force_password_change')
134 change_after = safe_int(should_change) or 0
134 change_after = safe_int(should_change) or 0
135 if should_change and now > change_after:
135 if should_change and now > change_after:
136 log.debug('User %s requires password change', user_obj)
136 log.debug('User %s requires password change', user_obj)
137 h.flash('You are required to change your password', 'warning',
137 h.flash('You are required to change your password', 'warning',
138 ignore_duplicate=True)
138 ignore_duplicate=True)
139
139
140 if view_name not in skip_user_views:
140 if view_name not in skip_user_views:
141 raise HTTPFound(
141 raise HTTPFound(
142 self.request.route_path('my_account_password'))
142 self.request.route_path('my_account_password'))
143
143
144 def _log_creation_exception(self, e, repo_name):
144 def _log_creation_exception(self, e, repo_name):
145 _ = self.request.translate
145 _ = self.request.translate
146 reason = None
146 reason = None
147 if len(e.args) == 2:
147 if len(e.args) == 2:
148 reason = e.args[1]
148 reason = e.args[1]
149
149
150 if reason == 'INVALID_CERTIFICATE':
150 if reason == 'INVALID_CERTIFICATE':
151 log.exception(
151 log.exception(
152 'Exception creating a repository: invalid certificate')
152 'Exception creating a repository: invalid certificate')
153 msg = (_('Error creating repository %s: invalid certificate')
153 msg = (_('Error creating repository %s: invalid certificate')
154 % repo_name)
154 % repo_name)
155 else:
155 else:
156 log.exception("Exception creating a repository")
156 log.exception("Exception creating a repository")
157 msg = (_('Error creating repository %s')
157 msg = (_('Error creating repository %s')
158 % repo_name)
158 % repo_name)
159 return msg
159 return msg
160
160
161 def _get_local_tmpl_context(self, include_app_defaults=True):
161 def _get_local_tmpl_context(self, include_app_defaults=True):
162 c = TemplateArgs()
162 c = TemplateArgs()
163 c.auth_user = self.request.user
163 c.auth_user = self.request.user
164 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
164 # TODO(marcink): migrate the usage of c.rhodecode_user to c.auth_user
165 c.rhodecode_user = self.request.user
165 c.rhodecode_user = self.request.user
166
166
167 if include_app_defaults:
167 if include_app_defaults:
168 from rhodecode.lib.base import attach_context_attributes
168 from rhodecode.lib.base import attach_context_attributes
169 attach_context_attributes(c, self.request, self.request.user.user_id)
169 attach_context_attributes(c, self.request, self.request.user.user_id)
170
170
171 return c
171 return c
172
172
173 def _get_template_context(self, tmpl_args, **kwargs):
173 def _get_template_context(self, tmpl_args, **kwargs):
174
174
175 local_tmpl_args = {
175 local_tmpl_args = {
176 'defaults': {},
176 'defaults': {},
177 'errors': {},
177 'errors': {},
178 'c': tmpl_args
178 'c': tmpl_args
179 }
179 }
180 local_tmpl_args.update(kwargs)
180 local_tmpl_args.update(kwargs)
181 return local_tmpl_args
181 return local_tmpl_args
182
182
183 def load_default_context(self):
183 def load_default_context(self):
184 """
184 """
185 example:
185 example:
186
186
187 def load_default_context(self):
187 def load_default_context(self):
188 c = self._get_local_tmpl_context()
188 c = self._get_local_tmpl_context()
189 c.custom_var = 'foobar'
189 c.custom_var = 'foobar'
190
190
191 return c
191 return c
192 """
192 """
193 raise NotImplementedError('Needs implementation in view class')
193 raise NotImplementedError('Needs implementation in view class')
194
194
195
195
196 class RepoAppView(BaseAppView):
196 class RepoAppView(BaseAppView):
197
197
198 def __init__(self, context, request):
198 def __init__(self, context, request):
199 super(RepoAppView, self).__init__(context, request)
199 super(RepoAppView, self).__init__(context, request)
200 self.db_repo = request.db_repo
200 self.db_repo = request.db_repo
201 self.db_repo_name = self.db_repo.repo_name
201 self.db_repo_name = self.db_repo.repo_name
202 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
202 self.db_repo_pull_requests = ScmModel().get_pull_requests(self.db_repo)
203
203
204 def _handle_missing_requirements(self, error):
204 def _handle_missing_requirements(self, error):
205 log.error(
205 log.error(
206 'Requirements are missing for repository %s: %s',
206 'Requirements are missing for repository %s: %s',
207 self.db_repo_name, safe_unicode(error))
207 self.db_repo_name, safe_unicode(error))
208
208
209 def _get_local_tmpl_context(self, include_app_defaults=True):
209 def _get_local_tmpl_context(self, include_app_defaults=True):
210 _ = self.request.translate
210 _ = self.request.translate
211 c = super(RepoAppView, self)._get_local_tmpl_context(
211 c = super(RepoAppView, self)._get_local_tmpl_context(
212 include_app_defaults=include_app_defaults)
212 include_app_defaults=include_app_defaults)
213
213
214 # register common vars for this type of view
214 # register common vars for this type of view
215 c.rhodecode_db_repo = self.db_repo
215 c.rhodecode_db_repo = self.db_repo
216 c.repo_name = self.db_repo_name
216 c.repo_name = self.db_repo_name
217 c.repository_pull_requests = self.db_repo_pull_requests
217 c.repository_pull_requests = self.db_repo_pull_requests
218 self.path_filter = PathFilter(None)
218 self.path_filter = PathFilter(None)
219
219
220 c.repository_requirements_missing = {}
220 c.repository_requirements_missing = {}
221 try:
221 try:
222 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
222 self.rhodecode_vcs_repo = self.db_repo.scm_instance()
223 if self.rhodecode_vcs_repo:
223 if self.rhodecode_vcs_repo:
224 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
224 path_perms = self.rhodecode_vcs_repo.get_path_permissions(
225 c.auth_user.username)
225 c.auth_user.username)
226 self.path_filter = PathFilter(path_perms)
226 self.path_filter = PathFilter(path_perms)
227 except RepositoryRequirementError as e:
227 except RepositoryRequirementError as e:
228 c.repository_requirements_missing = {'error': str(e)}
228 c.repository_requirements_missing = {'error': str(e)}
229 self._handle_missing_requirements(e)
229 self._handle_missing_requirements(e)
230 self.rhodecode_vcs_repo = None
230 self.rhodecode_vcs_repo = None
231
231
232 c.path_filter = self.path_filter # used by atom_feed_entry.mako
232 c.path_filter = self.path_filter # used by atom_feed_entry.mako
233
233
234 if self.rhodecode_vcs_repo is None:
234 if self.rhodecode_vcs_repo is None:
235 # unable to fetch this repo as vcs instance, report back to user
235 # unable to fetch this repo as vcs instance, report back to user
236 h.flash(_(
236 h.flash(_(
237 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
237 "The repository `%(repo_name)s` cannot be loaded in filesystem. "
238 "Please check if it exist, or is not damaged.") %
238 "Please check if it exist, or is not damaged.") %
239 {'repo_name': c.repo_name},
239 {'repo_name': c.repo_name},
240 category='error', ignore_duplicate=True)
240 category='error', ignore_duplicate=True)
241 if c.repository_requirements_missing:
241 if c.repository_requirements_missing:
242 route = self.request.matched_route.name
242 route = self.request.matched_route.name
243 if route.startswith(('edit_repo', 'repo_summary')):
243 if route.startswith(('edit_repo', 'repo_summary')):
244 # allow summary and edit repo on missing requirements
244 # allow summary and edit repo on missing requirements
245 return c
245 return c
246
246
247 raise HTTPFound(
247 raise HTTPFound(
248 h.route_path('repo_summary', repo_name=self.db_repo_name))
248 h.route_path('repo_summary', repo_name=self.db_repo_name))
249
249
250 else: # redirect if we don't show missing requirements
250 else: # redirect if we don't show missing requirements
251 raise HTTPFound(h.route_path('home'))
251 raise HTTPFound(h.route_path('home'))
252
252
253 c.has_origin_repo_read_perm = False
253 c.has_origin_repo_read_perm = False
254 if self.db_repo.fork:
254 if self.db_repo.fork:
255 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
255 c.has_origin_repo_read_perm = h.HasRepoPermissionAny(
256 'repository.write', 'repository.read', 'repository.admin')(
256 'repository.write', 'repository.read', 'repository.admin')(
257 self.db_repo.fork.repo_name, 'summary fork link')
257 self.db_repo.fork.repo_name, 'summary fork link')
258
258
259 return c
259 return c
260
260
261 def _get_f_path_unchecked(self, matchdict, default=None):
261 def _get_f_path_unchecked(self, matchdict, default=None):
262 """
262 """
263 Should only be used by redirects, everything else should call _get_f_path
263 Should only be used by redirects, everything else should call _get_f_path
264 """
264 """
265 f_path = matchdict.get('f_path')
265 f_path = matchdict.get('f_path')
266 if f_path:
266 if f_path:
267 # fix for multiple initial slashes that causes errors for GIT
267 # fix for multiple initial slashes that causes errors for GIT
268 return f_path.lstrip('/')
268 return f_path.lstrip('/')
269
269
270 return default
270 return default
271
271
272 def _get_f_path(self, matchdict, default=None):
272 def _get_f_path(self, matchdict, default=None):
273 f_path_match = self._get_f_path_unchecked(matchdict, default)
273 f_path_match = self._get_f_path_unchecked(matchdict, default)
274 return self.path_filter.assert_path_permissions(f_path_match)
274 return self.path_filter.assert_path_permissions(f_path_match)
275
275
276 def _get_general_setting(self, target_repo, settings_key, default=False):
276 def _get_general_setting(self, target_repo, settings_key, default=False):
277 settings_model = VcsSettingsModel(repo=target_repo)
277 settings_model = VcsSettingsModel(repo=target_repo)
278 settings = settings_model.get_general_settings()
278 settings = settings_model.get_general_settings()
279 return settings.get(settings_key, default)
279 return settings.get(settings_key, default)
280
280
281
281
282 class PathFilter(object):
282 class PathFilter(object):
283
283
284 # Expects and instance of BasePathPermissionChecker or None
284 # Expects and instance of BasePathPermissionChecker or None
285 def __init__(self, permission_checker):
285 def __init__(self, permission_checker):
286 self.permission_checker = permission_checker
286 self.permission_checker = permission_checker
287
287
288 def assert_path_permissions(self, path):
288 def assert_path_permissions(self, path):
289 if path and self.permission_checker and not self.permission_checker.has_access(path):
289 if path and self.permission_checker and not self.permission_checker.has_access(path):
290 raise HTTPForbidden()
290 raise HTTPForbidden()
291 return path
291 return path
292
292
293 def filter_patchset(self, patchset):
293 def filter_patchset(self, patchset):
294 if not self.permission_checker or not patchset:
294 if not self.permission_checker or not patchset:
295 return patchset, False
295 return patchset, False
296 had_filtered = False
296 had_filtered = False
297 filtered_patchset = []
297 filtered_patchset = []
298 for patch in patchset:
298 for patch in patchset:
299 filename = patch.get('filename', None)
299 filename = patch.get('filename', None)
300 if not filename or self.permission_checker.has_access(filename):
300 if not filename or self.permission_checker.has_access(filename):
301 filtered_patchset.append(patch)
301 filtered_patchset.append(patch)
302 else:
302 else:
303 had_filtered = True
303 had_filtered = True
304 if had_filtered:
304 if had_filtered:
305 if isinstance(patchset, diffs.LimitedDiffContainer):
305 if isinstance(patchset, diffs.LimitedDiffContainer):
306 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
306 filtered_patchset = diffs.LimitedDiffContainer(patchset.diff_limit, patchset.cur_diff_size, filtered_patchset)
307 return filtered_patchset, True
307 return filtered_patchset, True
308 else:
308 else:
309 return patchset, False
309 return patchset, False
310
310
311 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
311 def render_patchset_filtered(self, diffset, patchset, source_ref=None, target_ref=None):
312 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
312 filtered_patchset, has_hidden_changes = self.filter_patchset(patchset)
313 result = diffset.render_patchset(
313 result = diffset.render_patchset(
314 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
314 filtered_patchset, source_ref=source_ref, target_ref=target_ref)
315 result.has_hidden_changes = has_hidden_changes
315 result.has_hidden_changes = has_hidden_changes
316 return result
316 return result
317
317
318 def get_raw_patch(self, diff_processor):
318 def get_raw_patch(self, diff_processor):
319 if self.permission_checker is None:
319 if self.permission_checker is None:
320 return diff_processor.as_raw()
320 return diff_processor.as_raw()
321 elif self.permission_checker.has_full_access:
321 elif self.permission_checker.has_full_access:
322 return diff_processor.as_raw()
322 return diff_processor.as_raw()
323 else:
323 else:
324 return '# Repository has user-specific filters, raw patch generation is disabled.'
324 return '# Repository has user-specific filters, raw patch generation is disabled.'
325
325
326 @property
326 @property
327 def is_enabled(self):
327 def is_enabled(self):
328 return self.permission_checker is not None
328 return self.permission_checker is not None
329
329
330
330
331 class RepoGroupAppView(BaseAppView):
331 class RepoGroupAppView(BaseAppView):
332 def __init__(self, context, request):
332 def __init__(self, context, request):
333 super(RepoGroupAppView, self).__init__(context, request)
333 super(RepoGroupAppView, self).__init__(context, request)
334 self.db_repo_group = request.db_repo_group
334 self.db_repo_group = request.db_repo_group
335 self.db_repo_group_name = self.db_repo_group.group_name
335 self.db_repo_group_name = self.db_repo_group.group_name
336
336
337 def _get_local_tmpl_context(self, include_app_defaults=True):
338 _ = self.request.translate
339 c = super(RepoGroupAppView, self)._get_local_tmpl_context(
340 include_app_defaults=include_app_defaults)
341 c.repo_group = self.db_repo_group
342 return c
343
337 def _revoke_perms_on_yourself(self, form_result):
344 def _revoke_perms_on_yourself(self, form_result):
338 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
345 _updates = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
339 form_result['perm_updates'])
346 form_result['perm_updates'])
340 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
347 _additions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
341 form_result['perm_additions'])
348 form_result['perm_additions'])
342 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
349 _deletions = filter(lambda u: self._rhodecode_user.user_id == int(u[0]),
343 form_result['perm_deletions'])
350 form_result['perm_deletions'])
344 admin_perm = 'group.admin'
351 admin_perm = 'group.admin'
345 if _updates and _updates[0][1] != admin_perm or \
352 if _updates and _updates[0][1] != admin_perm or \
346 _additions and _additions[0][1] != admin_perm or \
353 _additions and _additions[0][1] != admin_perm or \
347 _deletions and _deletions[0][1] != admin_perm:
354 _deletions and _deletions[0][1] != admin_perm:
348 return True
355 return True
349 return False
356 return False
350
357
351
358
352 class UserGroupAppView(BaseAppView):
359 class UserGroupAppView(BaseAppView):
353 def __init__(self, context, request):
360 def __init__(self, context, request):
354 super(UserGroupAppView, self).__init__(context, request)
361 super(UserGroupAppView, self).__init__(context, request)
355 self.db_user_group = request.db_user_group
362 self.db_user_group = request.db_user_group
356 self.db_user_group_name = self.db_user_group.users_group_name
363 self.db_user_group_name = self.db_user_group.users_group_name
357
364
358
365
359 class UserAppView(BaseAppView):
366 class UserAppView(BaseAppView):
360 def __init__(self, context, request):
367 def __init__(self, context, request):
361 super(UserAppView, self).__init__(context, request)
368 super(UserAppView, self).__init__(context, request)
362 self.db_user = request.db_user
369 self.db_user = request.db_user
363 self.db_user_id = self.db_user.user_id
370 self.db_user_id = self.db_user.user_id
364
371
365 _ = self.request.translate
372 _ = self.request.translate
366 if not request.db_user_supports_default:
373 if not request.db_user_supports_default:
367 if self.db_user.username == User.DEFAULT_USER:
374 if self.db_user.username == User.DEFAULT_USER:
368 h.flash(_("Editing user `{}` is disabled.".format(
375 h.flash(_("Editing user `{}` is disabled.".format(
369 User.DEFAULT_USER)), category='warning')
376 User.DEFAULT_USER)), category='warning')
370 raise HTTPFound(h.route_path('users'))
377 raise HTTPFound(h.route_path('users'))
371
378
372
379
373 class DataGridAppView(object):
380 class DataGridAppView(object):
374 """
381 """
375 Common class to have re-usable grid rendering components
382 Common class to have re-usable grid rendering components
376 """
383 """
377
384
378 def _extract_ordering(self, request, column_map=None):
385 def _extract_ordering(self, request, column_map=None):
379 column_map = column_map or {}
386 column_map = column_map or {}
380 column_index = safe_int(request.GET.get('order[0][column]'))
387 column_index = safe_int(request.GET.get('order[0][column]'))
381 order_dir = request.GET.get(
388 order_dir = request.GET.get(
382 'order[0][dir]', 'desc')
389 'order[0][dir]', 'desc')
383 order_by = request.GET.get(
390 order_by = request.GET.get(
384 'columns[%s][data][sort]' % column_index, 'name_raw')
391 'columns[%s][data][sort]' % column_index, 'name_raw')
385
392
386 # translate datatable to DB columns
393 # translate datatable to DB columns
387 order_by = column_map.get(order_by) or order_by
394 order_by = column_map.get(order_by) or order_by
388
395
389 search_q = request.GET.get('search[value]')
396 search_q = request.GET.get('search[value]')
390 return search_q, order_by, order_dir
397 return search_q, order_by, order_dir
391
398
392 def _extract_chunk(self, request):
399 def _extract_chunk(self, request):
393 start = safe_int(request.GET.get('start'), 0)
400 start = safe_int(request.GET.get('start'), 0)
394 length = safe_int(request.GET.get('length'), 25)
401 length = safe_int(request.GET.get('length'), 25)
395 draw = safe_int(request.GET.get('draw'))
402 draw = safe_int(request.GET.get('draw'))
396 return draw, start, length
403 return draw, start, length
397
404
398 def _get_order_col(self, order_by, model):
405 def _get_order_col(self, order_by, model):
399 if isinstance(order_by, compat.string_types):
406 if isinstance(order_by, compat.string_types):
400 try:
407 try:
401 return operator.attrgetter(order_by)(model)
408 return operator.attrgetter(order_by)(model)
402 except AttributeError:
409 except AttributeError:
403 return None
410 return None
404 else:
411 else:
405 return order_by
412 return order_by
406
413
407
414
408 class BaseReferencesView(RepoAppView):
415 class BaseReferencesView(RepoAppView):
409 """
416 """
410 Base for reference view for branches, tags and bookmarks.
417 Base for reference view for branches, tags and bookmarks.
411 """
418 """
412 def load_default_context(self):
419 def load_default_context(self):
413 c = self._get_local_tmpl_context()
420 c = self._get_local_tmpl_context()
414
421
415
422
416 return c
423 return c
417
424
418 def load_refs_context(self, ref_items, partials_template):
425 def load_refs_context(self, ref_items, partials_template):
419 _render = self.request.get_partial_renderer(partials_template)
426 _render = self.request.get_partial_renderer(partials_template)
420 pre_load = ["author", "date", "message"]
427 pre_load = ["author", "date", "message"]
421
428
422 is_svn = h.is_svn(self.rhodecode_vcs_repo)
429 is_svn = h.is_svn(self.rhodecode_vcs_repo)
423 is_hg = h.is_hg(self.rhodecode_vcs_repo)
430 is_hg = h.is_hg(self.rhodecode_vcs_repo)
424
431
425 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
432 format_ref_id = get_format_ref_id(self.rhodecode_vcs_repo)
426
433
427 closed_refs = {}
434 closed_refs = {}
428 if is_hg:
435 if is_hg:
429 closed_refs = self.rhodecode_vcs_repo.branches_closed
436 closed_refs = self.rhodecode_vcs_repo.branches_closed
430
437
431 data = []
438 data = []
432 for ref_name, commit_id in ref_items:
439 for ref_name, commit_id in ref_items:
433 commit = self.rhodecode_vcs_repo.get_commit(
440 commit = self.rhodecode_vcs_repo.get_commit(
434 commit_id=commit_id, pre_load=pre_load)
441 commit_id=commit_id, pre_load=pre_load)
435 closed = ref_name in closed_refs
442 closed = ref_name in closed_refs
436
443
437 # TODO: johbo: Unify generation of reference links
444 # TODO: johbo: Unify generation of reference links
438 use_commit_id = '/' in ref_name or is_svn
445 use_commit_id = '/' in ref_name or is_svn
439
446
440 if use_commit_id:
447 if use_commit_id:
441 files_url = h.route_path(
448 files_url = h.route_path(
442 'repo_files',
449 'repo_files',
443 repo_name=self.db_repo_name,
450 repo_name=self.db_repo_name,
444 f_path=ref_name if is_svn else '',
451 f_path=ref_name if is_svn else '',
445 commit_id=commit_id)
452 commit_id=commit_id)
446
453
447 else:
454 else:
448 files_url = h.route_path(
455 files_url = h.route_path(
449 'repo_files',
456 'repo_files',
450 repo_name=self.db_repo_name,
457 repo_name=self.db_repo_name,
451 f_path=ref_name if is_svn else '',
458 f_path=ref_name if is_svn else '',
452 commit_id=ref_name,
459 commit_id=ref_name,
453 _query=dict(at=ref_name))
460 _query=dict(at=ref_name))
454
461
455 data.append({
462 data.append({
456 "name": _render('name', ref_name, files_url, closed),
463 "name": _render('name', ref_name, files_url, closed),
457 "name_raw": ref_name,
464 "name_raw": ref_name,
458 "date": _render('date', commit.date),
465 "date": _render('date', commit.date),
459 "date_raw": datetime_to_time(commit.date),
466 "date_raw": datetime_to_time(commit.date),
460 "author": _render('author', commit.author),
467 "author": _render('author', commit.author),
461 "commit": _render(
468 "commit": _render(
462 'commit', commit.message, commit.raw_id, commit.idx),
469 'commit', commit.message, commit.raw_id, commit.idx),
463 "commit_raw": commit.idx,
470 "commit_raw": commit.idx,
464 "compare": _render(
471 "compare": _render(
465 'compare', format_ref_id(ref_name, commit.raw_id)),
472 'compare', format_ref_id(ref_name, commit.raw_id)),
466 })
473 })
467
474
468 return data
475 return data
469
476
470
477
471 class RepoRoutePredicate(object):
478 class RepoRoutePredicate(object):
472 def __init__(self, val, config):
479 def __init__(self, val, config):
473 self.val = val
480 self.val = val
474
481
475 def text(self):
482 def text(self):
476 return 'repo_route = %s' % self.val
483 return 'repo_route = %s' % self.val
477
484
478 phash = text
485 phash = text
479
486
480 def __call__(self, info, request):
487 def __call__(self, info, request):
481 if hasattr(request, 'vcs_call'):
488 if hasattr(request, 'vcs_call'):
482 # skip vcs calls
489 # skip vcs calls
483 return
490 return
484
491
485 repo_name = info['match']['repo_name']
492 repo_name = info['match']['repo_name']
486 repo_model = repo.RepoModel()
493 repo_model = repo.RepoModel()
487
494
488 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
495 by_name_match = repo_model.get_by_repo_name(repo_name, cache=False)
489
496
490 def redirect_if_creating(route_info, db_repo):
497 def redirect_if_creating(route_info, db_repo):
491 skip_views = ['edit_repo_advanced_delete']
498 skip_views = ['edit_repo_advanced_delete']
492 route = route_info['route']
499 route = route_info['route']
493 # we should skip delete view so we can actually "remove" repositories
500 # we should skip delete view so we can actually "remove" repositories
494 # if they get stuck in creating state.
501 # if they get stuck in creating state.
495 if route.name in skip_views:
502 if route.name in skip_views:
496 return
503 return
497
504
498 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
505 if db_repo.repo_state in [repo.Repository.STATE_PENDING]:
499 repo_creating_url = request.route_path(
506 repo_creating_url = request.route_path(
500 'repo_creating', repo_name=db_repo.repo_name)
507 'repo_creating', repo_name=db_repo.repo_name)
501 raise HTTPFound(repo_creating_url)
508 raise HTTPFound(repo_creating_url)
502
509
503 if by_name_match:
510 if by_name_match:
504 # register this as request object we can re-use later
511 # register this as request object we can re-use later
505 request.db_repo = by_name_match
512 request.db_repo = by_name_match
506 redirect_if_creating(info, by_name_match)
513 redirect_if_creating(info, by_name_match)
507 return True
514 return True
508
515
509 by_id_match = repo_model.get_repo_by_id(repo_name)
516 by_id_match = repo_model.get_repo_by_id(repo_name)
510 if by_id_match:
517 if by_id_match:
511 request.db_repo = by_id_match
518 request.db_repo = by_id_match
512 redirect_if_creating(info, by_id_match)
519 redirect_if_creating(info, by_id_match)
513 return True
520 return True
514
521
515 return False
522 return False
516
523
517
524
518 class RepoForbidArchivedRoutePredicate(object):
525 class RepoForbidArchivedRoutePredicate(object):
519 def __init__(self, val, config):
526 def __init__(self, val, config):
520 self.val = val
527 self.val = val
521
528
522 def text(self):
529 def text(self):
523 return 'repo_forbid_archived = %s' % self.val
530 return 'repo_forbid_archived = %s' % self.val
524
531
525 phash = text
532 phash = text
526
533
527 def __call__(self, info, request):
534 def __call__(self, info, request):
528 _ = request.translate
535 _ = request.translate
529 rhodecode_db_repo = request.db_repo
536 rhodecode_db_repo = request.db_repo
530
537
531 log.debug(
538 log.debug(
532 '%s checking if archived flag for repo for %s',
539 '%s checking if archived flag for repo for %s',
533 self.__class__.__name__, rhodecode_db_repo.repo_name)
540 self.__class__.__name__, rhodecode_db_repo.repo_name)
534
541
535 if rhodecode_db_repo.archived:
542 if rhodecode_db_repo.archived:
536 log.warning('Current view is not supported for archived repo:%s',
543 log.warning('Current view is not supported for archived repo:%s',
537 rhodecode_db_repo.repo_name)
544 rhodecode_db_repo.repo_name)
538
545
539 h.flash(
546 h.flash(
540 h.literal(_('Action not supported for archived repository.')),
547 h.literal(_('Action not supported for archived repository.')),
541 category='warning')
548 category='warning')
542 summary_url = request.route_path(
549 summary_url = request.route_path(
543 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
550 'repo_summary', repo_name=rhodecode_db_repo.repo_name)
544 raise HTTPFound(summary_url)
551 raise HTTPFound(summary_url)
545 return True
552 return True
546
553
547
554
548 class RepoTypeRoutePredicate(object):
555 class RepoTypeRoutePredicate(object):
549 def __init__(self, val, config):
556 def __init__(self, val, config):
550 self.val = val or ['hg', 'git', 'svn']
557 self.val = val or ['hg', 'git', 'svn']
551
558
552 def text(self):
559 def text(self):
553 return 'repo_accepted_type = %s' % self.val
560 return 'repo_accepted_type = %s' % self.val
554
561
555 phash = text
562 phash = text
556
563
557 def __call__(self, info, request):
564 def __call__(self, info, request):
558 if hasattr(request, 'vcs_call'):
565 if hasattr(request, 'vcs_call'):
559 # skip vcs calls
566 # skip vcs calls
560 return
567 return
561
568
562 rhodecode_db_repo = request.db_repo
569 rhodecode_db_repo = request.db_repo
563
570
564 log.debug(
571 log.debug(
565 '%s checking repo type for %s in %s',
572 '%s checking repo type for %s in %s',
566 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
573 self.__class__.__name__, rhodecode_db_repo.repo_type, self.val)
567
574
568 if rhodecode_db_repo.repo_type in self.val:
575 if rhodecode_db_repo.repo_type in self.val:
569 return True
576 return True
570 else:
577 else:
571 log.warning('Current view is not supported for repo type:%s',
578 log.warning('Current view is not supported for repo type:%s',
572 rhodecode_db_repo.repo_type)
579 rhodecode_db_repo.repo_type)
573 return False
580 return False
574
581
575
582
576 class RepoGroupRoutePredicate(object):
583 class RepoGroupRoutePredicate(object):
577 def __init__(self, val, config):
584 def __init__(self, val, config):
578 self.val = val
585 self.val = val
579
586
580 def text(self):
587 def text(self):
581 return 'repo_group_route = %s' % self.val
588 return 'repo_group_route = %s' % self.val
582
589
583 phash = text
590 phash = text
584
591
585 def __call__(self, info, request):
592 def __call__(self, info, request):
586 if hasattr(request, 'vcs_call'):
593 if hasattr(request, 'vcs_call'):
587 # skip vcs calls
594 # skip vcs calls
588 return
595 return
589
596
590 repo_group_name = info['match']['repo_group_name']
597 repo_group_name = info['match']['repo_group_name']
591 repo_group_model = repo_group.RepoGroupModel()
598 repo_group_model = repo_group.RepoGroupModel()
592 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
599 by_name_match = repo_group_model.get_by_group_name(repo_group_name, cache=False)
593
600
594 if by_name_match:
601 if by_name_match:
595 # register this as request object we can re-use later
602 # register this as request object we can re-use later
596 request.db_repo_group = by_name_match
603 request.db_repo_group = by_name_match
597 return True
604 return True
598
605
599 return False
606 return False
600
607
601
608
602 class UserGroupRoutePredicate(object):
609 class UserGroupRoutePredicate(object):
603 def __init__(self, val, config):
610 def __init__(self, val, config):
604 self.val = val
611 self.val = val
605
612
606 def text(self):
613 def text(self):
607 return 'user_group_route = %s' % self.val
614 return 'user_group_route = %s' % self.val
608
615
609 phash = text
616 phash = text
610
617
611 def __call__(self, info, request):
618 def __call__(self, info, request):
612 if hasattr(request, 'vcs_call'):
619 if hasattr(request, 'vcs_call'):
613 # skip vcs calls
620 # skip vcs calls
614 return
621 return
615
622
616 user_group_id = info['match']['user_group_id']
623 user_group_id = info['match']['user_group_id']
617 user_group_model = user_group.UserGroup()
624 user_group_model = user_group.UserGroup()
618 by_id_match = user_group_model.get(user_group_id, cache=False)
625 by_id_match = user_group_model.get(user_group_id, cache=False)
619
626
620 if by_id_match:
627 if by_id_match:
621 # register this as request object we can re-use later
628 # register this as request object we can re-use later
622 request.db_user_group = by_id_match
629 request.db_user_group = by_id_match
623 return True
630 return True
624
631
625 return False
632 return False
626
633
627
634
628 class UserRoutePredicateBase(object):
635 class UserRoutePredicateBase(object):
629 supports_default = None
636 supports_default = None
630
637
631 def __init__(self, val, config):
638 def __init__(self, val, config):
632 self.val = val
639 self.val = val
633
640
634 def text(self):
641 def text(self):
635 raise NotImplementedError()
642 raise NotImplementedError()
636
643
637 def __call__(self, info, request):
644 def __call__(self, info, request):
638 if hasattr(request, 'vcs_call'):
645 if hasattr(request, 'vcs_call'):
639 # skip vcs calls
646 # skip vcs calls
640 return
647 return
641
648
642 user_id = info['match']['user_id']
649 user_id = info['match']['user_id']
643 user_model = user.User()
650 user_model = user.User()
644 by_id_match = user_model.get(user_id, cache=False)
651 by_id_match = user_model.get(user_id, cache=False)
645
652
646 if by_id_match:
653 if by_id_match:
647 # register this as request object we can re-use later
654 # register this as request object we can re-use later
648 request.db_user = by_id_match
655 request.db_user = by_id_match
649 request.db_user_supports_default = self.supports_default
656 request.db_user_supports_default = self.supports_default
650 return True
657 return True
651
658
652 return False
659 return False
653
660
654
661
655 class UserRoutePredicate(UserRoutePredicateBase):
662 class UserRoutePredicate(UserRoutePredicateBase):
656 supports_default = False
663 supports_default = False
657
664
658 def text(self):
665 def text(self):
659 return 'user_route = %s' % self.val
666 return 'user_route = %s' % self.val
660
667
661 phash = text
668 phash = text
662
669
663
670
664 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
671 class UserRouteWithDefaultPredicate(UserRoutePredicateBase):
665 supports_default = True
672 supports_default = True
666
673
667 def text(self):
674 def text(self):
668 return 'user_with_default_route = %s' % self.val
675 return 'user_with_default_route = %s' % self.val
669
676
670 phash = text
677 phash = text
671
678
672
679
673 def includeme(config):
680 def includeme(config):
674 config.add_route_predicate(
681 config.add_route_predicate(
675 'repo_route', RepoRoutePredicate)
682 'repo_route', RepoRoutePredicate)
676 config.add_route_predicate(
683 config.add_route_predicate(
677 'repo_accepted_types', RepoTypeRoutePredicate)
684 'repo_accepted_types', RepoTypeRoutePredicate)
678 config.add_route_predicate(
685 config.add_route_predicate(
679 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
686 'repo_forbid_when_archived', RepoForbidArchivedRoutePredicate)
680 config.add_route_predicate(
687 config.add_route_predicate(
681 'repo_group_route', RepoGroupRoutePredicate)
688 'repo_group_route', RepoGroupRoutePredicate)
682 config.add_route_predicate(
689 config.add_route_predicate(
683 'user_group_route', UserGroupRoutePredicate)
690 'user_group_route', UserGroupRoutePredicate)
684 config.add_route_predicate(
691 config.add_route_predicate(
685 'user_route_with_default', UserRouteWithDefaultPredicate)
692 'user_route_with_default', UserRouteWithDefaultPredicate)
686 config.add_route_predicate(
693 config.add_route_predicate(
687 'user_route', UserRoutePredicate)
694 'user_route', UserRoutePredicate)
@@ -1,587 +1,596 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2019 RhodeCode GmbH
3 # Copyright (C) 2016-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import re
21 import re
22 import logging
22 import logging
23 import collections
23 import collections
24
24
25 from pyramid.view import view_config
25 from pyramid.view import view_config
26
26
27 from rhodecode.apps._base import BaseAppView
27 from rhodecode.apps._base import BaseAppView
28 from rhodecode.lib import helpers as h
28 from rhodecode.lib import helpers as h
29 from rhodecode.lib.auth import (
29 from rhodecode.lib.auth import (
30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator,
30 LoginRequired, NotAnonymous, HasRepoGroupPermissionAnyDecorator,
31 CSRFRequired)
31 CSRFRequired)
32 from rhodecode.lib.index import searcher_from_config
32 from rhodecode.lib.index import searcher_from_config
33 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
33 from rhodecode.lib.utils2 import safe_unicode, str2bool, safe_int
34 from rhodecode.lib.ext_json import json
34 from rhodecode.lib.ext_json import json
35 from rhodecode.model.db import (
35 from rhodecode.model.db import (
36 func, true, or_, in_filter_generator, Repository, RepoGroup, User, UserGroup)
36 func, true, or_, in_filter_generator, Repository, RepoGroup, User, UserGroup)
37 from rhodecode.model.repo import RepoModel
37 from rhodecode.model.repo import RepoModel
38 from rhodecode.model.repo_group import RepoGroupModel
38 from rhodecode.model.repo_group import RepoGroupModel
39 from rhodecode.model.scm import RepoGroupList, RepoList
39 from rhodecode.model.scm import RepoGroupList, RepoList
40 from rhodecode.model.user import UserModel
40 from rhodecode.model.user import UserModel
41 from rhodecode.model.user_group import UserGroupModel
41 from rhodecode.model.user_group import UserGroupModel
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class HomeView(BaseAppView):
46 class HomeView(BaseAppView):
47
47
48 def load_default_context(self):
48 def load_default_context(self):
49 c = self._get_local_tmpl_context()
49 c = self._get_local_tmpl_context()
50 c.user = c.auth_user.get_instance()
50 c.user = c.auth_user.get_instance()
51
51
52 return c
52 return c
53
53
54 @LoginRequired()
54 @LoginRequired()
55 @view_config(
55 @view_config(
56 route_name='user_autocomplete_data', request_method='GET',
56 route_name='user_autocomplete_data', request_method='GET',
57 renderer='json_ext', xhr=True)
57 renderer='json_ext', xhr=True)
58 def user_autocomplete_data(self):
58 def user_autocomplete_data(self):
59 self.load_default_context()
59 self.load_default_context()
60 query = self.request.GET.get('query')
60 query = self.request.GET.get('query')
61 active = str2bool(self.request.GET.get('active') or True)
61 active = str2bool(self.request.GET.get('active') or True)
62 include_groups = str2bool(self.request.GET.get('user_groups'))
62 include_groups = str2bool(self.request.GET.get('user_groups'))
63 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
63 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
64 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
64 skip_default_user = str2bool(self.request.GET.get('skip_default_user'))
65
65
66 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
66 log.debug('generating user list, query:%s, active:%s, with_groups:%s',
67 query, active, include_groups)
67 query, active, include_groups)
68
68
69 _users = UserModel().get_users(
69 _users = UserModel().get_users(
70 name_contains=query, only_active=active)
70 name_contains=query, only_active=active)
71
71
72 def maybe_skip_default_user(usr):
72 def maybe_skip_default_user(usr):
73 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
73 if skip_default_user and usr['username'] == UserModel.cls.DEFAULT_USER:
74 return False
74 return False
75 return True
75 return True
76 _users = filter(maybe_skip_default_user, _users)
76 _users = filter(maybe_skip_default_user, _users)
77
77
78 if include_groups:
78 if include_groups:
79 # extend with user groups
79 # extend with user groups
80 _user_groups = UserGroupModel().get_user_groups(
80 _user_groups = UserGroupModel().get_user_groups(
81 name_contains=query, only_active=active,
81 name_contains=query, only_active=active,
82 expand_groups=expand_groups)
82 expand_groups=expand_groups)
83 _users = _users + _user_groups
83 _users = _users + _user_groups
84
84
85 return {'suggestions': _users}
85 return {'suggestions': _users}
86
86
87 @LoginRequired()
87 @LoginRequired()
88 @NotAnonymous()
88 @NotAnonymous()
89 @view_config(
89 @view_config(
90 route_name='user_group_autocomplete_data', request_method='GET',
90 route_name='user_group_autocomplete_data', request_method='GET',
91 renderer='json_ext', xhr=True)
91 renderer='json_ext', xhr=True)
92 def user_group_autocomplete_data(self):
92 def user_group_autocomplete_data(self):
93 self.load_default_context()
93 self.load_default_context()
94 query = self.request.GET.get('query')
94 query = self.request.GET.get('query')
95 active = str2bool(self.request.GET.get('active') or True)
95 active = str2bool(self.request.GET.get('active') or True)
96 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
96 expand_groups = str2bool(self.request.GET.get('user_groups_expand'))
97
97
98 log.debug('generating user group list, query:%s, active:%s',
98 log.debug('generating user group list, query:%s, active:%s',
99 query, active)
99 query, active)
100
100
101 _user_groups = UserGroupModel().get_user_groups(
101 _user_groups = UserGroupModel().get_user_groups(
102 name_contains=query, only_active=active,
102 name_contains=query, only_active=active,
103 expand_groups=expand_groups)
103 expand_groups=expand_groups)
104 _user_groups = _user_groups
104 _user_groups = _user_groups
105
105
106 return {'suggestions': _user_groups}
106 return {'suggestions': _user_groups}
107
107
108 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
108 def _get_repo_list(self, name_contains=None, repo_type=None, limit=20):
109 org_query = name_contains
109 org_query = name_contains
110 allowed_ids = self._rhodecode_user.repo_acl_ids(
110 allowed_ids = self._rhodecode_user.repo_acl_ids(
111 ['repository.read', 'repository.write', 'repository.admin'],
111 ['repository.read', 'repository.write', 'repository.admin'],
112 cache=False, name_filter=name_contains) or [-1]
112 cache=False, name_filter=name_contains) or [-1]
113
113
114 query = Repository.query()\
114 query = Repository.query()\
115 .order_by(func.length(Repository.repo_name))\
115 .order_by(func.length(Repository.repo_name))\
116 .order_by(Repository.repo_name)\
116 .order_by(Repository.repo_name)\
117 .filter(Repository.archived.isnot(true()))\
117 .filter(Repository.archived.isnot(true()))\
118 .filter(or_(
118 .filter(or_(
119 # generate multiple IN to fix limitation problems
119 # generate multiple IN to fix limitation problems
120 *in_filter_generator(Repository.repo_id, allowed_ids)
120 *in_filter_generator(Repository.repo_id, allowed_ids)
121 ))
121 ))
122
122
123 if repo_type:
123 if repo_type:
124 query = query.filter(Repository.repo_type == repo_type)
124 query = query.filter(Repository.repo_type == repo_type)
125
125
126 if name_contains:
126 if name_contains:
127 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
127 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
128 query = query.filter(
128 query = query.filter(
129 Repository.repo_name.ilike(ilike_expression))
129 Repository.repo_name.ilike(ilike_expression))
130 query = query.limit(limit)
130 query = query.limit(limit)
131
131
132 acl_iter = query
132 acl_iter = query
133
133
134 return [
134 return [
135 {
135 {
136 'id': obj.repo_name,
136 'id': obj.repo_name,
137 'value': org_query,
137 'value': org_query,
138 'value_display': obj.repo_name,
138 'value_display': obj.repo_name,
139 'text': obj.repo_name,
139 'text': obj.repo_name,
140 'type': 'repo',
140 'type': 'repo',
141 'repo_id': obj.repo_id,
141 'repo_id': obj.repo_id,
142 'repo_type': obj.repo_type,
142 'repo_type': obj.repo_type,
143 'private': obj.private,
143 'private': obj.private,
144 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
144 'url': h.route_path('repo_summary', repo_name=obj.repo_name)
145 }
145 }
146 for obj in acl_iter]
146 for obj in acl_iter]
147
147
148 def _get_repo_group_list(self, name_contains=None, limit=20):
148 def _get_repo_group_list(self, name_contains=None, limit=20):
149 org_query = name_contains
149 org_query = name_contains
150 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
150 allowed_ids = self._rhodecode_user.repo_group_acl_ids(
151 ['group.read', 'group.write', 'group.admin'],
151 ['group.read', 'group.write', 'group.admin'],
152 cache=False, name_filter=name_contains) or [-1]
152 cache=False, name_filter=name_contains) or [-1]
153
153
154 query = RepoGroup.query()\
154 query = RepoGroup.query()\
155 .order_by(func.length(RepoGroup.group_name))\
155 .order_by(func.length(RepoGroup.group_name))\
156 .order_by(RepoGroup.group_name) \
156 .order_by(RepoGroup.group_name) \
157 .filter(or_(
157 .filter(or_(
158 # generate multiple IN to fix limitation problems
158 # generate multiple IN to fix limitation problems
159 *in_filter_generator(RepoGroup.group_id, allowed_ids)
159 *in_filter_generator(RepoGroup.group_id, allowed_ids)
160 ))
160 ))
161
161
162 if name_contains:
162 if name_contains:
163 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
163 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
164 query = query.filter(
164 query = query.filter(
165 RepoGroup.group_name.ilike(ilike_expression))
165 RepoGroup.group_name.ilike(ilike_expression))
166 query = query.limit(limit)
166 query = query.limit(limit)
167
167
168 acl_iter = query
168 acl_iter = query
169
169
170 return [
170 return [
171 {
171 {
172 'id': obj.group_name,
172 'id': obj.group_name,
173 'value': org_query,
173 'value': org_query,
174 'value_display': obj.group_name,
174 'value_display': obj.group_name,
175 'text': obj.group_name,
175 'text': obj.group_name,
176 'type': 'repo_group',
176 'type': 'repo_group',
177 'repo_group_id': obj.group_id,
177 'repo_group_id': obj.group_id,
178 'url': h.route_path(
178 'url': h.route_path(
179 'repo_group_home', repo_group_name=obj.group_name)
179 'repo_group_home', repo_group_name=obj.group_name)
180 }
180 }
181 for obj in acl_iter]
181 for obj in acl_iter]
182
182
183 def _get_user_list(self, name_contains=None, limit=20):
183 def _get_user_list(self, name_contains=None, limit=20):
184 org_query = name_contains
184 org_query = name_contains
185 if not name_contains:
185 if not name_contains:
186 return []
186 return []
187
187
188 name_contains = re.compile('(?:user:)(.+)').findall(name_contains)
188 name_contains = re.compile('(?:user:)(.+)').findall(name_contains)
189 if len(name_contains) != 1:
189 if len(name_contains) != 1:
190 return []
190 return []
191 name_contains = name_contains[0]
191 name_contains = name_contains[0]
192
192
193 query = User.query()\
193 query = User.query()\
194 .order_by(func.length(User.username))\
194 .order_by(func.length(User.username))\
195 .order_by(User.username) \
195 .order_by(User.username) \
196 .filter(User.username != User.DEFAULT_USER)
196 .filter(User.username != User.DEFAULT_USER)
197
197
198 if name_contains:
198 if name_contains:
199 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
199 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
200 query = query.filter(
200 query = query.filter(
201 User.username.ilike(ilike_expression))
201 User.username.ilike(ilike_expression))
202 query = query.limit(limit)
202 query = query.limit(limit)
203
203
204 acl_iter = query
204 acl_iter = query
205
205
206 return [
206 return [
207 {
207 {
208 'id': obj.user_id,
208 'id': obj.user_id,
209 'value': org_query,
209 'value': org_query,
210 'value_display': obj.username,
210 'value_display': obj.username,
211 'type': 'user',
211 'type': 'user',
212 'icon_link': h.gravatar_url(obj.email, 30),
212 'icon_link': h.gravatar_url(obj.email, 30),
213 'url': h.route_path(
213 'url': h.route_path(
214 'user_profile', username=obj.username)
214 'user_profile', username=obj.username)
215 }
215 }
216 for obj in acl_iter]
216 for obj in acl_iter]
217
217
218 def _get_user_groups_list(self, name_contains=None, limit=20):
218 def _get_user_groups_list(self, name_contains=None, limit=20):
219 org_query = name_contains
219 org_query = name_contains
220 if not name_contains:
220 if not name_contains:
221 return []
221 return []
222
222
223 name_contains = re.compile('(?:user_group:)(.+)').findall(name_contains)
223 name_contains = re.compile('(?:user_group:)(.+)').findall(name_contains)
224 if len(name_contains) != 1:
224 if len(name_contains) != 1:
225 return []
225 return []
226 name_contains = name_contains[0]
226 name_contains = name_contains[0]
227
227
228 query = UserGroup.query()\
228 query = UserGroup.query()\
229 .order_by(func.length(UserGroup.users_group_name))\
229 .order_by(func.length(UserGroup.users_group_name))\
230 .order_by(UserGroup.users_group_name)
230 .order_by(UserGroup.users_group_name)
231
231
232 if name_contains:
232 if name_contains:
233 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
233 ilike_expression = u'%{}%'.format(safe_unicode(name_contains))
234 query = query.filter(
234 query = query.filter(
235 UserGroup.users_group_name.ilike(ilike_expression))
235 UserGroup.users_group_name.ilike(ilike_expression))
236 query = query.limit(limit)
236 query = query.limit(limit)
237
237
238 acl_iter = query
238 acl_iter = query
239
239
240 return [
240 return [
241 {
241 {
242 'id': obj.users_group_id,
242 'id': obj.users_group_id,
243 'value': org_query,
243 'value': org_query,
244 'value_display': obj.users_group_name,
244 'value_display': obj.users_group_name,
245 'type': 'user_group',
245 'type': 'user_group',
246 'url': h.route_path(
246 'url': h.route_path(
247 'user_group_profile', user_group_name=obj.users_group_name)
247 'user_group_profile', user_group_name=obj.users_group_name)
248 }
248 }
249 for obj in acl_iter]
249 for obj in acl_iter]
250
250
251 def _get_hash_commit_list(self, auth_user, searcher, query):
251 def _get_hash_commit_list(self, auth_user, searcher, query):
252 org_query = query
252 org_query = query
253 if not query or len(query) < 3 or not searcher:
253 if not query or len(query) < 3 or not searcher:
254 return []
254 return []
255
255
256 commit_hashes = re.compile('(?:commit:)([0-9a-f]{2,40})').findall(query)
256 commit_hashes = re.compile('(?:commit:)([0-9a-f]{2,40})').findall(query)
257
257
258 if len(commit_hashes) != 1:
258 if len(commit_hashes) != 1:
259 return []
259 return []
260 commit_hash = commit_hashes[0]
260 commit_hash = commit_hashes[0]
261
261
262 result = searcher.search(
262 result = searcher.search(
263 'commit_id:{}*'.format(commit_hash), 'commit', auth_user,
263 'commit_id:{}*'.format(commit_hash), 'commit', auth_user,
264 raise_on_exc=False)
264 raise_on_exc=False)
265
265
266 return [
266 return [
267 {
267 {
268 'id': entry['commit_id'],
268 'id': entry['commit_id'],
269 'value': org_query,
269 'value': org_query,
270 'value_display': 'repo `{}` commit: {}'.format(
270 'value_display': 'repo `{}` commit: {}'.format(
271 entry['repository'], entry['commit_id']),
271 entry['repository'], entry['commit_id']),
272 'type': 'commit',
272 'type': 'commit',
273 'repo': entry['repository'],
273 'repo': entry['repository'],
274 'url': h.route_path(
274 'url': h.route_path(
275 'repo_commit',
275 'repo_commit',
276 repo_name=entry['repository'], commit_id=entry['commit_id'])
276 repo_name=entry['repository'], commit_id=entry['commit_id'])
277 }
277 }
278 for entry in result['results']]
278 for entry in result['results']]
279
279
280 @LoginRequired()
280 @LoginRequired()
281 @view_config(
281 @view_config(
282 route_name='repo_list_data', request_method='GET',
282 route_name='repo_list_data', request_method='GET',
283 renderer='json_ext', xhr=True)
283 renderer='json_ext', xhr=True)
284 def repo_list_data(self):
284 def repo_list_data(self):
285 _ = self.request.translate
285 _ = self.request.translate
286 self.load_default_context()
286 self.load_default_context()
287
287
288 query = self.request.GET.get('query')
288 query = self.request.GET.get('query')
289 repo_type = self.request.GET.get('repo_type')
289 repo_type = self.request.GET.get('repo_type')
290 log.debug('generating repo list, query:%s, repo_type:%s',
290 log.debug('generating repo list, query:%s, repo_type:%s',
291 query, repo_type)
291 query, repo_type)
292
292
293 res = []
293 res = []
294 repos = self._get_repo_list(query, repo_type=repo_type)
294 repos = self._get_repo_list(query, repo_type=repo_type)
295 if repos:
295 if repos:
296 res.append({
296 res.append({
297 'text': _('Repositories'),
297 'text': _('Repositories'),
298 'children': repos
298 'children': repos
299 })
299 })
300
300
301 data = {
301 data = {
302 'more': False,
302 'more': False,
303 'results': res
303 'results': res
304 }
304 }
305 return data
305 return data
306
306
307 @LoginRequired()
307 @LoginRequired()
308 @view_config(
308 @view_config(
309 route_name='repo_group_list_data', request_method='GET',
309 route_name='repo_group_list_data', request_method='GET',
310 renderer='json_ext', xhr=True)
310 renderer='json_ext', xhr=True)
311 def repo_group_list_data(self):
311 def repo_group_list_data(self):
312 _ = self.request.translate
312 _ = self.request.translate
313 self.load_default_context()
313 self.load_default_context()
314
314
315 query = self.request.GET.get('query')
315 query = self.request.GET.get('query')
316
316
317 log.debug('generating repo group list, query:%s',
317 log.debug('generating repo group list, query:%s',
318 query)
318 query)
319
319
320 res = []
320 res = []
321 repo_groups = self._get_repo_group_list(query)
321 repo_groups = self._get_repo_group_list(query)
322 if repo_groups:
322 if repo_groups:
323 res.append({
323 res.append({
324 'text': _('Repository Groups'),
324 'text': _('Repository Groups'),
325 'children': repo_groups
325 'children': repo_groups
326 })
326 })
327
327
328 data = {
328 data = {
329 'more': False,
329 'more': False,
330 'results': res
330 'results': res
331 }
331 }
332 return data
332 return data
333
333
334 def _get_default_search_queries(self, search_context, searcher, query):
334 def _get_default_search_queries(self, search_context, searcher, query):
335 if not searcher:
335 if not searcher:
336 return []
336 return []
337
337 is_es_6 = searcher.is_es_6
338 is_es_6 = searcher.is_es_6
338
339
339 queries = []
340 queries = []
340 repo_group_name, repo_name, repo_context = None, None, None
341 repo_group_name, repo_name, repo_context = None, None, None
341
342
342 # repo group context
343 # repo group context
343 if search_context.get('search_context[repo_group_name]'):
344 if search_context.get('search_context[repo_group_name]'):
344 repo_group_name = search_context.get('search_context[repo_group_name]')
345 repo_group_name = search_context.get('search_context[repo_group_name]')
345 if search_context.get('search_context[repo_name]'):
346 if search_context.get('search_context[repo_name]'):
346 repo_name = search_context.get('search_context[repo_name]')
347 repo_name = search_context.get('search_context[repo_name]')
347 repo_context = search_context.get('search_context[repo_view_type]')
348 repo_context = search_context.get('search_context[repo_view_type]')
348
349
349 if is_es_6 and repo_name:
350 if is_es_6 and repo_name:
350 # files
351 # files
351 def query_modifier():
352 def query_modifier():
352 qry = query
353 qry = query
353 return {'q': qry, 'type': 'content'}
354 return {'q': qry, 'type': 'content'}
354 label = u'File search for `{}` in this repository.'.format(query)
355 label = u'File search for `{}` in this repository.'.format(query)
355 queries.append(
356 queries.append(
356 {
357 {
357 'id': -10,
358 'id': -10,
358 'value': query,
359 'value': query,
359 'value_display': label,
360 'value_display': label,
360 'type': 'search',
361 'type': 'search',
361 'url': h.route_path('search_repo',
362 'url': h.route_path('search_repo',
362 repo_name=repo_name,
363 repo_name=repo_name,
363 _query=query_modifier())
364 _query=query_modifier())
364 }
365 }
365 )
366 )
366
367
367 # commits
368 # commits
368 def query_modifier():
369 def query_modifier():
369 qry = query
370 qry = query
370 return {'q': qry, 'type': 'commit'}
371 return {'q': qry, 'type': 'commit'}
371
372
372 label = u'Commit search for `{}` in this repository.'.format(query)
373 label = u'Commit search for `{}` in this repository.'.format(query)
373 queries.append(
374 queries.append(
374 {
375 {
375 'id': -10,
376 'id': -10,
376 'value': query,
377 'value': query,
377 'value_display': label,
378 'value_display': label,
378 'type': 'search',
379 'type': 'search',
379 'url': h.route_path('search_repo',
380 'url': h.route_path('search_repo',
380 repo_name=repo_name,
381 repo_name=repo_name,
381 _query=query_modifier())
382 _query=query_modifier())
382 }
383 }
383 )
384 )
384
385
385 elif is_es_6 and repo_group_name:
386 elif is_es_6 and repo_group_name:
386 # files
387 # files
387 def query_modifier():
388 def query_modifier():
388 qry = query
389 qry = query
389 return {'q': qry, 'type': 'content'}
390 return {'q': qry, 'type': 'content'}
390
391
391 label = u'File search for `{}` in this repository group'.format(query)
392 label = u'File search for `{}` in this repository group'.format(query)
392 queries.append(
393 queries.append(
393 {
394 {
394 'id': -20,
395 'id': -20,
395 'value': query,
396 'value': query,
396 'value_display': label,
397 'value_display': label,
397 'type': 'search',
398 'type': 'search',
398 'url': h.route_path('search_repo_group',
399 'url': h.route_path('search_repo_group',
399 repo_group_name=repo_group_name,
400 repo_group_name=repo_group_name,
400 _query=query_modifier())
401 _query=query_modifier())
401 }
402 }
402 )
403 )
403
404
404 # commits
405 # commits
405 def query_modifier():
406 def query_modifier():
406 qry = query
407 qry = query
407 return {'q': qry, 'type': 'commit'}
408 return {'q': qry, 'type': 'commit'}
408
409
409 label = u'Commit search for `{}` in this repository group'.format(query)
410 label = u'Commit search for `{}` in this repository group'.format(query)
410 queries.append(
411 queries.append(
411 {
412 {
412 'id': -20,
413 'id': -20,
413 'value': query,
414 'value': query,
414 'value_display': label,
415 'value_display': label,
415 'type': 'search',
416 'type': 'search',
416 'url': h.route_path('search_repo_group',
417 'url': h.route_path('search_repo_group',
417 repo_group_name=repo_group_name,
418 repo_group_name=repo_group_name,
418 _query=query_modifier())
419 _query=query_modifier())
419 }
420 }
420 )
421 )
421
422
422 if not queries:
423 if not queries:
423 queries.append(
424 queries.append(
424 {
425 {
425 'id': -1,
426 'id': -1,
426 'value': query,
427 'value': query,
427 'value_display': u'Search for: `{}`'.format(query),
428 'value_display': u'Commit search for: `{}`'.format(query),
428 'type': 'search',
429 'type': 'search',
429 'url': h.route_path('search',
430 'url': h.route_path('search',
430 _query={'q': query, 'type': 'content'})
431 _query={'q': query, 'type': 'content'})
431 }
432 })
432 )
433 queries.append(
434 {
435 'id': -1,
436 'value': query,
437 'value_display': u'File search for: `{}`'.format(query),
438 'type': 'search',
439 'url': h.route_path('search',
440 _query={'q': query, 'type': 'commit'})
441 })
433
442
434 return queries
443 return queries
435
444
436 @LoginRequired()
445 @LoginRequired()
437 @view_config(
446 @view_config(
438 route_name='goto_switcher_data', request_method='GET',
447 route_name='goto_switcher_data', request_method='GET',
439 renderer='json_ext', xhr=True)
448 renderer='json_ext', xhr=True)
440 def goto_switcher_data(self):
449 def goto_switcher_data(self):
441 c = self.load_default_context()
450 c = self.load_default_context()
442
451
443 _ = self.request.translate
452 _ = self.request.translate
444
453
445 query = self.request.GET.get('query')
454 query = self.request.GET.get('query')
446 log.debug('generating main filter data, query %s', query)
455 log.debug('generating main filter data, query %s', query)
447
456
448 res = []
457 res = []
449 if not query:
458 if not query:
450 return {'suggestions': res}
459 return {'suggestions': res}
451
460
452 searcher = searcher_from_config(self.request.registry.settings)
461 searcher = searcher_from_config(self.request.registry.settings)
453 for _q in self._get_default_search_queries(self.request.GET, searcher, query):
462 for _q in self._get_default_search_queries(self.request.GET, searcher, query):
454 res.append(_q)
463 res.append(_q)
455
464
456 repo_group_id = safe_int(self.request.GET.get('search_context[repo_group_id]'))
465 repo_group_id = safe_int(self.request.GET.get('search_context[repo_group_id]'))
457 if repo_group_id:
466 if repo_group_id:
458 repo_group = RepoGroup.get(repo_group_id)
467 repo_group = RepoGroup.get(repo_group_id)
459 composed_hint = '{}/{}'.format(repo_group.group_name, query)
468 composed_hint = '{}/{}'.format(repo_group.group_name, query)
460 show_hint = not query.startswith(repo_group.group_name)
469 show_hint = not query.startswith(repo_group.group_name)
461 if repo_group and show_hint:
470 if repo_group and show_hint:
462 hint = u'Repository search inside: `{}`'.format(composed_hint)
471 hint = u'Repository search inside: `{}`'.format(composed_hint)
463 res.append({
472 res.append({
464 'id': -1,
473 'id': -1,
465 'value': composed_hint,
474 'value': composed_hint,
466 'value_display': hint,
475 'value_display': hint,
467 'type': 'hint',
476 'type': 'hint',
468 'url': ""
477 'url': ""
469 })
478 })
470
479
471 repo_groups = self._get_repo_group_list(query)
480 repo_groups = self._get_repo_group_list(query)
472 for serialized_repo_group in repo_groups:
481 for serialized_repo_group in repo_groups:
473 res.append(serialized_repo_group)
482 res.append(serialized_repo_group)
474
483
475 repos = self._get_repo_list(query)
484 repos = self._get_repo_list(query)
476 for serialized_repo in repos:
485 for serialized_repo in repos:
477 res.append(serialized_repo)
486 res.append(serialized_repo)
478
487
479 # TODO(marcink): should all logged in users be allowed to search others?
488 # TODO(marcink): should all logged in users be allowed to search others?
480 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
489 allowed_user_search = self._rhodecode_user.username != User.DEFAULT_USER
481 if allowed_user_search:
490 if allowed_user_search:
482 users = self._get_user_list(query)
491 users = self._get_user_list(query)
483 for serialized_user in users:
492 for serialized_user in users:
484 res.append(serialized_user)
493 res.append(serialized_user)
485
494
486 user_groups = self._get_user_groups_list(query)
495 user_groups = self._get_user_groups_list(query)
487 for serialized_user_group in user_groups:
496 for serialized_user_group in user_groups:
488 res.append(serialized_user_group)
497 res.append(serialized_user_group)
489
498
490 commits = self._get_hash_commit_list(c.auth_user, searcher, query)
499 commits = self._get_hash_commit_list(c.auth_user, searcher, query)
491 if commits:
500 if commits:
492 unique_repos = collections.OrderedDict()
501 unique_repos = collections.OrderedDict()
493 for commit in commits:
502 for commit in commits:
494 repo_name = commit['repo']
503 repo_name = commit['repo']
495 unique_repos.setdefault(repo_name, []).append(commit)
504 unique_repos.setdefault(repo_name, []).append(commit)
496
505
497 for repo, commits in unique_repos.items():
506 for repo, commits in unique_repos.items():
498 for commit in commits:
507 for commit in commits:
499 res.append(commit)
508 res.append(commit)
500
509
501 return {'suggestions': res}
510 return {'suggestions': res}
502
511
503 def _get_groups_and_repos(self, repo_group_id=None):
512 def _get_groups_and_repos(self, repo_group_id=None):
504 # repo groups groups
513 # repo groups groups
505 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
514 repo_group_list = RepoGroup.get_all_repo_groups(group_id=repo_group_id)
506 _perms = ['group.read', 'group.write', 'group.admin']
515 _perms = ['group.read', 'group.write', 'group.admin']
507 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
516 repo_group_list_acl = RepoGroupList(repo_group_list, perm_set=_perms)
508 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
517 repo_group_data = RepoGroupModel().get_repo_groups_as_dict(
509 repo_group_list=repo_group_list_acl, admin=False)
518 repo_group_list=repo_group_list_acl, admin=False)
510
519
511 # repositories
520 # repositories
512 repo_list = Repository.get_all_repos(group_id=repo_group_id)
521 repo_list = Repository.get_all_repos(group_id=repo_group_id)
513 _perms = ['repository.read', 'repository.write', 'repository.admin']
522 _perms = ['repository.read', 'repository.write', 'repository.admin']
514 repo_list_acl = RepoList(repo_list, perm_set=_perms)
523 repo_list_acl = RepoList(repo_list, perm_set=_perms)
515 repo_data = RepoModel().get_repos_as_dict(
524 repo_data = RepoModel().get_repos_as_dict(
516 repo_list=repo_list_acl, admin=False)
525 repo_list=repo_list_acl, admin=False)
517
526
518 return repo_data, repo_group_data
527 return repo_data, repo_group_data
519
528
520 @LoginRequired()
529 @LoginRequired()
521 @view_config(
530 @view_config(
522 route_name='home', request_method='GET',
531 route_name='home', request_method='GET',
523 renderer='rhodecode:templates/index.mako')
532 renderer='rhodecode:templates/index.mako')
524 def main_page(self):
533 def main_page(self):
525 c = self.load_default_context()
534 c = self.load_default_context()
526 c.repo_group = None
535 c.repo_group = None
527
536
528 repo_data, repo_group_data = self._get_groups_and_repos()
537 repo_data, repo_group_data = self._get_groups_and_repos()
529 # json used to render the grids
538 # json used to render the grids
530 c.repos_data = json.dumps(repo_data)
539 c.repos_data = json.dumps(repo_data)
531 c.repo_groups_data = json.dumps(repo_group_data)
540 c.repo_groups_data = json.dumps(repo_group_data)
532
541
533 return self._get_template_context(c)
542 return self._get_template_context(c)
534
543
535 @LoginRequired()
544 @LoginRequired()
536 @HasRepoGroupPermissionAnyDecorator(
545 @HasRepoGroupPermissionAnyDecorator(
537 'group.read', 'group.write', 'group.admin')
546 'group.read', 'group.write', 'group.admin')
538 @view_config(
547 @view_config(
539 route_name='repo_group_home', request_method='GET',
548 route_name='repo_group_home', request_method='GET',
540 renderer='rhodecode:templates/index_repo_group.mako')
549 renderer='rhodecode:templates/index_repo_group.mako')
541 @view_config(
550 @view_config(
542 route_name='repo_group_home_slash', request_method='GET',
551 route_name='repo_group_home_slash', request_method='GET',
543 renderer='rhodecode:templates/index_repo_group.mako')
552 renderer='rhodecode:templates/index_repo_group.mako')
544 def repo_group_main_page(self):
553 def repo_group_main_page(self):
545 c = self.load_default_context()
554 c = self.load_default_context()
546 c.repo_group = self.request.db_repo_group
555 c.repo_group = self.request.db_repo_group
547 repo_data, repo_group_data = self._get_groups_and_repos(
556 repo_data, repo_group_data = self._get_groups_and_repos(
548 c.repo_group.group_id)
557 c.repo_group.group_id)
549
558
550 # json used to render the grids
559 # json used to render the grids
551 c.repos_data = json.dumps(repo_data)
560 c.repos_data = json.dumps(repo_data)
552 c.repo_groups_data = json.dumps(repo_group_data)
561 c.repo_groups_data = json.dumps(repo_group_data)
553
562
554 return self._get_template_context(c)
563 return self._get_template_context(c)
555
564
556 @LoginRequired()
565 @LoginRequired()
557 @CSRFRequired()
566 @CSRFRequired()
558 @view_config(
567 @view_config(
559 route_name='markup_preview', request_method='POST',
568 route_name='markup_preview', request_method='POST',
560 renderer='string', xhr=True)
569 renderer='string', xhr=True)
561 def markup_preview(self):
570 def markup_preview(self):
562 # Technically a CSRF token is not needed as no state changes with this
571 # Technically a CSRF token is not needed as no state changes with this
563 # call. However, as this is a POST is better to have it, so automated
572 # call. However, as this is a POST is better to have it, so automated
564 # tools don't flag it as potential CSRF.
573 # tools don't flag it as potential CSRF.
565 # Post is required because the payload could be bigger than the maximum
574 # Post is required because the payload could be bigger than the maximum
566 # allowed by GET.
575 # allowed by GET.
567
576
568 text = self.request.POST.get('text')
577 text = self.request.POST.get('text')
569 renderer = self.request.POST.get('renderer') or 'rst'
578 renderer = self.request.POST.get('renderer') or 'rst'
570 if text:
579 if text:
571 return h.render(text, renderer=renderer, mentions=True)
580 return h.render(text, renderer=renderer, mentions=True)
572 return ''
581 return ''
573
582
574 @LoginRequired()
583 @LoginRequired()
575 @CSRFRequired()
584 @CSRFRequired()
576 @view_config(
585 @view_config(
577 route_name='store_user_session_value', request_method='POST',
586 route_name='store_user_session_value', request_method='POST',
578 renderer='string', xhr=True)
587 renderer='string', xhr=True)
579 def store_user_session_attr(self):
588 def store_user_session_attr(self):
580 key = self.request.POST.get('key')
589 key = self.request.POST.get('key')
581 val = self.request.POST.get('val')
590 val = self.request.POST.get('val')
582
591
583 existing_value = self.request.session.get(key)
592 existing_value = self.request.session.get(key)
584 if existing_value != val:
593 if existing_value != val:
585 self.request.session[key] = val
594 self.request.session[key] = val
586
595
587 return 'stored:{}'.format(key)
596 return 'stored:{}'.format(key)
@@ -1,161 +1,164 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2011-2019 RhodeCode GmbH
3 # Copyright (C) 2011-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import logging
21 import logging
22 import urllib
22 import urllib
23 from pyramid.view import view_config
23 from pyramid.view import view_config
24 from webhelpers.util import update_params
24 from webhelpers.util import update_params
25
25
26 from rhodecode.apps._base import BaseAppView, RepoAppView, RepoGroupAppView
26 from rhodecode.apps._base import BaseAppView, RepoAppView, RepoGroupAppView
27 from rhodecode.lib.auth import (
27 from rhodecode.lib.auth import (
28 LoginRequired, HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
28 LoginRequired, HasRepoPermissionAnyDecorator, HasRepoGroupPermissionAnyDecorator)
29 from rhodecode.lib.helpers import Page
29 from rhodecode.lib.helpers import Page
30 from rhodecode.lib.utils2 import safe_str
30 from rhodecode.lib.utils2 import safe_str
31 from rhodecode.lib.index import searcher_from_config
31 from rhodecode.lib.index import searcher_from_config
32 from rhodecode.model import validation_schema
32 from rhodecode.model import validation_schema
33 from rhodecode.model.validation_schema.schemas import search_schema
33 from rhodecode.model.validation_schema.schemas import search_schema
34
34
35 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
36
36
37
37
38 def perform_search(request, tmpl_context, repo_name=None, repo_group_name=None):
38 def perform_search(request, tmpl_context, repo_name=None, repo_group_name=None):
39 searcher = searcher_from_config(request.registry.settings)
39 searcher = searcher_from_config(request.registry.settings)
40 formatted_results = []
40 formatted_results = []
41 execution_time = ''
41 execution_time = ''
42
42
43 schema = search_schema.SearchParamsSchema()
43 schema = search_schema.SearchParamsSchema()
44
44 search_tags = []
45 search_params = {}
45 search_params = {}
46 errors = []
46 errors = []
47 try:
47 try:
48 search_params = schema.deserialize(
48 search_params = schema.deserialize(
49 dict(
49 dict(
50 search_query=request.GET.get('q'),
50 search_query=request.GET.get('q'),
51 search_type=request.GET.get('type'),
51 search_type=request.GET.get('type'),
52 search_sort=request.GET.get('sort'),
52 search_sort=request.GET.get('sort'),
53 search_max_lines=request.GET.get('max_lines'),
53 search_max_lines=request.GET.get('max_lines'),
54 page_limit=request.GET.get('page_limit'),
54 page_limit=request.GET.get('page_limit'),
55 requested_page=request.GET.get('page'),
55 requested_page=request.GET.get('page'),
56 )
56 )
57 )
57 )
58 except validation_schema.Invalid as e:
58 except validation_schema.Invalid as e:
59 errors = e.children
59 errors = e.children
60
60
61 def url_generator(**kw):
61 def url_generator(**kw):
62 q = urllib.quote(safe_str(search_query))
62 q = urllib.quote(safe_str(search_query))
63 return update_params(
63 return update_params(
64 "?q=%s&type=%s&max_lines=%s" % (
64 "?q=%s&type=%s&max_lines=%s" % (
65 q, safe_str(search_type), search_max_lines), **kw)
65 q, safe_str(search_type), search_max_lines), **kw)
66
66
67 c = tmpl_context
67 c = tmpl_context
68 search_query = search_params.get('search_query')
68 search_query = search_params.get('search_query')
69 search_type = search_params.get('search_type')
69 search_type = search_params.get('search_type')
70 search_sort = search_params.get('search_sort')
70 search_sort = search_params.get('search_sort')
71 search_max_lines = search_params.get('search_max_lines')
71 search_max_lines = search_params.get('search_max_lines')
72 if search_params.get('search_query'):
72 if search_params.get('search_query'):
73 page_limit = search_params['page_limit']
73 page_limit = search_params['page_limit']
74 requested_page = search_params['requested_page']
74 requested_page = search_params['requested_page']
75
75
76 try:
76 try:
77 search_result = searcher.search(
77 search_result = searcher.search(
78 search_query, search_type, c.auth_user, repo_name, repo_group_name,
78 search_query, search_type, c.auth_user, repo_name, repo_group_name,
79 requested_page=requested_page, page_limit=page_limit, sort=search_sort)
79 requested_page=requested_page, page_limit=page_limit, sort=search_sort)
80
80
81 formatted_results = Page(
81 formatted_results = Page(
82 search_result['results'], page=requested_page,
82 search_result['results'], page=requested_page,
83 item_count=search_result['count'],
83 item_count=search_result['count'],
84 items_per_page=page_limit, url=url_generator)
84 items_per_page=page_limit, url=url_generator)
85 finally:
85 finally:
86 searcher.cleanup()
86 searcher.cleanup()
87
87
88 search_tags = searcher.extract_search_tags(search_query)
89
88 if not search_result['error']:
90 if not search_result['error']:
89 execution_time = '%s results (%.3f seconds)' % (
91 execution_time = '%s results (%.3f seconds)' % (
90 search_result['count'],
92 search_result['count'],
91 search_result['runtime'])
93 search_result['runtime'])
92 elif not errors:
94 elif not errors:
93 node = schema['search_query']
95 node = schema['search_query']
94 errors = [
96 errors = [
95 validation_schema.Invalid(node, search_result['error'])]
97 validation_schema.Invalid(node, search_result['error'])]
96
98
97 c.perm_user = c.auth_user
99 c.perm_user = c.auth_user
98 c.repo_name = repo_name
100 c.repo_name = repo_name
99 c.repo_group_name = repo_group_name
101 c.repo_group_name = repo_group_name
100 c.sort = search_sort
102 c.sort = search_sort
101 c.url_generator = url_generator
103 c.url_generator = url_generator
102 c.errors = errors
104 c.errors = errors
103 c.formatted_results = formatted_results
105 c.formatted_results = formatted_results
104 c.runtime = execution_time
106 c.runtime = execution_time
105 c.cur_query = search_query
107 c.cur_query = search_query
106 c.search_type = search_type
108 c.search_type = search_type
107 c.searcher = searcher
109 c.searcher = searcher
110 c.search_tags = search_tags
108
111
109
112
110 class SearchView(BaseAppView):
113 class SearchView(BaseAppView):
111 def load_default_context(self):
114 def load_default_context(self):
112 c = self._get_local_tmpl_context()
115 c = self._get_local_tmpl_context()
113 return c
116 return c
114
117
115 @LoginRequired()
118 @LoginRequired()
116 @view_config(
119 @view_config(
117 route_name='search', request_method='GET',
120 route_name='search', request_method='GET',
118 renderer='rhodecode:templates/search/search.mako')
121 renderer='rhodecode:templates/search/search.mako')
119 def search(self):
122 def search(self):
120 c = self.load_default_context()
123 c = self.load_default_context()
121 perform_search(self.request, c)
124 perform_search(self.request, c)
122 return self._get_template_context(c)
125 return self._get_template_context(c)
123
126
124
127
125 class SearchRepoView(RepoAppView):
128 class SearchRepoView(RepoAppView):
126 def load_default_context(self):
129 def load_default_context(self):
127 c = self._get_local_tmpl_context()
130 c = self._get_local_tmpl_context()
128 c.active = 'search'
131 c.active = 'search'
129 return c
132 return c
130
133
131 @LoginRequired()
134 @LoginRequired()
132 @HasRepoPermissionAnyDecorator(
135 @HasRepoPermissionAnyDecorator(
133 'repository.read', 'repository.write', 'repository.admin')
136 'repository.read', 'repository.write', 'repository.admin')
134 @view_config(
137 @view_config(
135 route_name='search_repo', request_method='GET',
138 route_name='search_repo', request_method='GET',
136 renderer='rhodecode:templates/search/search.mako')
139 renderer='rhodecode:templates/search/search.mako')
137 @view_config(
140 @view_config(
138 route_name='search_repo_alt', request_method='GET',
141 route_name='search_repo_alt', request_method='GET',
139 renderer='rhodecode:templates/search/search.mako')
142 renderer='rhodecode:templates/search/search.mako')
140 def search_repo(self):
143 def search_repo(self):
141 c = self.load_default_context()
144 c = self.load_default_context()
142 perform_search(self.request, c, repo_name=self.db_repo_name)
145 perform_search(self.request, c, repo_name=self.db_repo_name)
143 return self._get_template_context(c)
146 return self._get_template_context(c)
144
147
145
148
146 class SearchRepoGroupView(RepoGroupAppView):
149 class SearchRepoGroupView(RepoGroupAppView):
147 def load_default_context(self):
150 def load_default_context(self):
148 c = self._get_local_tmpl_context()
151 c = self._get_local_tmpl_context()
149 c.active = 'search'
152 c.active = 'search'
150 return c
153 return c
151
154
152 @LoginRequired()
155 @LoginRequired()
153 @HasRepoGroupPermissionAnyDecorator(
156 @HasRepoGroupPermissionAnyDecorator(
154 'group.read', 'group.write', 'group.admin')
157 'group.read', 'group.write', 'group.admin')
155 @view_config(
158 @view_config(
156 route_name='search_repo_group', request_method='GET',
159 route_name='search_repo_group', request_method='GET',
157 renderer='rhodecode:templates/search/search.mako')
160 renderer='rhodecode:templates/search/search.mako')
158 def search_repo_group(self):
161 def search_repo_group(self):
159 c = self.load_default_context()
162 c = self.load_default_context()
160 perform_search(self.request, c, repo_group_name=self.db_repo_group_name)
163 perform_search(self.request, c, repo_group_name=self.db_repo_group_name)
161 return self._get_template_context(c)
164 return self._get_template_context(c)
@@ -1,2020 +1,2042 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2019 RhodeCode GmbH
3 # Copyright (C) 2010-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Helper functions
22 Helper functions
23
23
24 Consists of functions to typically be used within templates, but also
24 Consists of functions to typically be used within templates, but also
25 available to Controllers. This module is available to both as 'h'.
25 available to Controllers. This module is available to both as 'h'.
26 """
26 """
27
27
28 import os
28 import os
29 import random
29 import random
30 import hashlib
30 import hashlib
31 import StringIO
31 import StringIO
32 import textwrap
32 import textwrap
33 import urllib
33 import urllib
34 import math
34 import math
35 import logging
35 import logging
36 import re
36 import re
37 import time
37 import time
38 import string
38 import string
39 import hashlib
39 import hashlib
40 from collections import OrderedDict
40 from collections import OrderedDict
41
41
42 import pygments
42 import pygments
43 import itertools
43 import itertools
44 import fnmatch
44 import fnmatch
45 import bleach
45 import bleach
46
46
47 from pyramid import compat
47 from pyramid import compat
48 from datetime import datetime
48 from datetime import datetime
49 from functools import partial
49 from functools import partial
50 from pygments.formatters.html import HtmlFormatter
50 from pygments.formatters.html import HtmlFormatter
51 from pygments.lexers import (
51 from pygments.lexers import (
52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
52 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
53
53
54 from pyramid.threadlocal import get_current_request
54 from pyramid.threadlocal import get_current_request
55
55
56 from webhelpers.html import literal, HTML, escape
56 from webhelpers.html import literal, HTML, escape
57 from webhelpers.html.tools import *
57 from webhelpers.html.tools import *
58 from webhelpers.html.builder import make_tag
58 from webhelpers.html.builder import make_tag
59 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
59 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
60 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
60 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
61 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
61 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
62 submit, text, password, textarea, title, ul, xml_declaration, radio
62 submit, text, password, textarea, title, ul, xml_declaration, radio
63 from webhelpers.html.tools import auto_link, button_to, highlight, \
63 from webhelpers.html.tools import auto_link, button_to, highlight, \
64 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
64 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
65 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
65 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
66 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
66 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
67 replace_whitespace, urlify, truncate, wrap_paragraphs
67 replace_whitespace, urlify, truncate, wrap_paragraphs
68 from webhelpers.date import time_ago_in_words
68 from webhelpers.date import time_ago_in_words
69 from webhelpers.paginate import Page as _Page
69 from webhelpers.paginate import Page as _Page
70 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
70 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
71 convert_boolean_attrs, NotGiven, _make_safe_id_component
71 convert_boolean_attrs, NotGiven, _make_safe_id_component
72 from webhelpers2.number import format_byte_size
72 from webhelpers2.number import format_byte_size
73
73
74 from rhodecode.lib.action_parser import action_parser
74 from rhodecode.lib.action_parser import action_parser
75 from rhodecode.lib.ext_json import json
75 from rhodecode.lib.ext_json import json
76 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
76 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
77 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
77 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
78 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
78 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
79 AttributeDict, safe_int, md5, md5_safe
79 AttributeDict, safe_int, md5, md5_safe
80 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
80 from rhodecode.lib.markup_renderer import MarkupRenderer, relative_links
81 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
81 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
82 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
82 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
83 from rhodecode.lib.index.search_utils import get_matching_line_offsets
83 from rhodecode.lib.index.search_utils import get_matching_line_offsets
84 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
84 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
85 from rhodecode.model.changeset_status import ChangesetStatusModel
85 from rhodecode.model.changeset_status import ChangesetStatusModel
86 from rhodecode.model.db import Permission, User, Repository
86 from rhodecode.model.db import Permission, User, Repository
87 from rhodecode.model.repo_group import RepoGroupModel
87 from rhodecode.model.repo_group import RepoGroupModel
88 from rhodecode.model.settings import IssueTrackerSettingsModel
88 from rhodecode.model.settings import IssueTrackerSettingsModel
89
89
90
90
91 log = logging.getLogger(__name__)
91 log = logging.getLogger(__name__)
92
92
93
93
94 DEFAULT_USER = User.DEFAULT_USER
94 DEFAULT_USER = User.DEFAULT_USER
95 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
95 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
96
96
97
97
98 def asset(path, ver=None, **kwargs):
98 def asset(path, ver=None, **kwargs):
99 """
99 """
100 Helper to generate a static asset file path for rhodecode assets
100 Helper to generate a static asset file path for rhodecode assets
101
101
102 eg. h.asset('images/image.png', ver='3923')
102 eg. h.asset('images/image.png', ver='3923')
103
103
104 :param path: path of asset
104 :param path: path of asset
105 :param ver: optional version query param to append as ?ver=
105 :param ver: optional version query param to append as ?ver=
106 """
106 """
107 request = get_current_request()
107 request = get_current_request()
108 query = {}
108 query = {}
109 query.update(kwargs)
109 query.update(kwargs)
110 if ver:
110 if ver:
111 query = {'ver': ver}
111 query = {'ver': ver}
112 return request.static_path(
112 return request.static_path(
113 'rhodecode:public/{}'.format(path), _query=query)
113 'rhodecode:public/{}'.format(path), _query=query)
114
114
115
115
116 default_html_escape_table = {
116 default_html_escape_table = {
117 ord('&'): u'&amp;',
117 ord('&'): u'&amp;',
118 ord('<'): u'&lt;',
118 ord('<'): u'&lt;',
119 ord('>'): u'&gt;',
119 ord('>'): u'&gt;',
120 ord('"'): u'&quot;',
120 ord('"'): u'&quot;',
121 ord("'"): u'&#39;',
121 ord("'"): u'&#39;',
122 }
122 }
123
123
124
124
125 def html_escape(text, html_escape_table=default_html_escape_table):
125 def html_escape(text, html_escape_table=default_html_escape_table):
126 """Produce entities within text."""
126 """Produce entities within text."""
127 return text.translate(html_escape_table)
127 return text.translate(html_escape_table)
128
128
129
129
130 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
130 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
131 """
131 """
132 Truncate string ``s`` at the first occurrence of ``sub``.
132 Truncate string ``s`` at the first occurrence of ``sub``.
133
133
134 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
134 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
135 """
135 """
136 suffix_if_chopped = suffix_if_chopped or ''
136 suffix_if_chopped = suffix_if_chopped or ''
137 pos = s.find(sub)
137 pos = s.find(sub)
138 if pos == -1:
138 if pos == -1:
139 return s
139 return s
140
140
141 if inclusive:
141 if inclusive:
142 pos += len(sub)
142 pos += len(sub)
143
143
144 chopped = s[:pos]
144 chopped = s[:pos]
145 left = s[pos:].strip()
145 left = s[pos:].strip()
146
146
147 if left and suffix_if_chopped:
147 if left and suffix_if_chopped:
148 chopped += suffix_if_chopped
148 chopped += suffix_if_chopped
149
149
150 return chopped
150 return chopped
151
151
152
152
153 def shorter(text, size=20):
153 def shorter(text, size=20):
154 postfix = '...'
154 postfix = '...'
155 if len(text) > size:
155 if len(text) > size:
156 return text[:size - len(postfix)] + postfix
156 return text[:size - len(postfix)] + postfix
157 return text
157 return text
158
158
159
159
160 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
160 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
161 """
161 """
162 Reset button
162 Reset button
163 """
163 """
164 _set_input_attrs(attrs, type, name, value)
164 _set_input_attrs(attrs, type, name, value)
165 _set_id_attr(attrs, id, name)
165 _set_id_attr(attrs, id, name)
166 convert_boolean_attrs(attrs, ["disabled"])
166 convert_boolean_attrs(attrs, ["disabled"])
167 return HTML.input(**attrs)
167 return HTML.input(**attrs)
168
168
169 reset = _reset
169 reset = _reset
170 safeid = _make_safe_id_component
170 safeid = _make_safe_id_component
171
171
172
172
173 def branding(name, length=40):
173 def branding(name, length=40):
174 return truncate(name, length, indicator="")
174 return truncate(name, length, indicator="")
175
175
176
176
177 def FID(raw_id, path):
177 def FID(raw_id, path):
178 """
178 """
179 Creates a unique ID for filenode based on it's hash of path and commit
179 Creates a unique ID for filenode based on it's hash of path and commit
180 it's safe to use in urls
180 it's safe to use in urls
181
181
182 :param raw_id:
182 :param raw_id:
183 :param path:
183 :param path:
184 """
184 """
185
185
186 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
186 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
187
187
188
188
189 class _GetError(object):
189 class _GetError(object):
190 """Get error from form_errors, and represent it as span wrapped error
190 """Get error from form_errors, and represent it as span wrapped error
191 message
191 message
192
192
193 :param field_name: field to fetch errors for
193 :param field_name: field to fetch errors for
194 :param form_errors: form errors dict
194 :param form_errors: form errors dict
195 """
195 """
196
196
197 def __call__(self, field_name, form_errors):
197 def __call__(self, field_name, form_errors):
198 tmpl = """<span class="error_msg">%s</span>"""
198 tmpl = """<span class="error_msg">%s</span>"""
199 if form_errors and field_name in form_errors:
199 if form_errors and field_name in form_errors:
200 return literal(tmpl % form_errors.get(field_name))
200 return literal(tmpl % form_errors.get(field_name))
201
201
202 get_error = _GetError()
202 get_error = _GetError()
203
203
204
204
205 class _ToolTip(object):
205 class _ToolTip(object):
206
206
207 def __call__(self, tooltip_title, trim_at=50):
207 def __call__(self, tooltip_title, trim_at=50):
208 """
208 """
209 Special function just to wrap our text into nice formatted
209 Special function just to wrap our text into nice formatted
210 autowrapped text
210 autowrapped text
211
211
212 :param tooltip_title:
212 :param tooltip_title:
213 """
213 """
214 tooltip_title = escape(tooltip_title)
214 tooltip_title = escape(tooltip_title)
215 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
215 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
216 return tooltip_title
216 return tooltip_title
217 tooltip = _ToolTip()
217 tooltip = _ToolTip()
218
218
219
219
220 def files_breadcrumbs(repo_name, commit_id, file_path):
220 def files_breadcrumbs(repo_name, commit_id, file_path):
221 if isinstance(file_path, str):
221 if isinstance(file_path, str):
222 file_path = safe_unicode(file_path)
222 file_path = safe_unicode(file_path)
223
223
224 # TODO: johbo: Is this always a url like path, or is this operating
224 # TODO: johbo: Is this always a url like path, or is this operating
225 # system dependent?
225 # system dependent?
226 path_segments = file_path.split('/')
226 path_segments = file_path.split('/')
227
227
228 repo_name_html = escape(repo_name)
228 repo_name_html = escape(repo_name)
229 if len(path_segments) == 1 and path_segments[0] == '':
229 if len(path_segments) == 1 and path_segments[0] == '':
230 url_segments = [repo_name_html]
230 url_segments = [repo_name_html]
231 else:
231 else:
232 url_segments = [
232 url_segments = [
233 link_to(
233 link_to(
234 repo_name_html,
234 repo_name_html,
235 route_path(
235 route_path(
236 'repo_files',
236 'repo_files',
237 repo_name=repo_name,
237 repo_name=repo_name,
238 commit_id=commit_id,
238 commit_id=commit_id,
239 f_path=''),
239 f_path=''),
240 class_='pjax-link')]
240 class_='pjax-link')]
241
241
242 last_cnt = len(path_segments) - 1
242 last_cnt = len(path_segments) - 1
243 for cnt, segment in enumerate(path_segments):
243 for cnt, segment in enumerate(path_segments):
244 if not segment:
244 if not segment:
245 continue
245 continue
246 segment_html = escape(segment)
246 segment_html = escape(segment)
247
247
248 if cnt != last_cnt:
248 if cnt != last_cnt:
249 url_segments.append(
249 url_segments.append(
250 link_to(
250 link_to(
251 segment_html,
251 segment_html,
252 route_path(
252 route_path(
253 'repo_files',
253 'repo_files',
254 repo_name=repo_name,
254 repo_name=repo_name,
255 commit_id=commit_id,
255 commit_id=commit_id,
256 f_path='/'.join(path_segments[:cnt + 1])),
256 f_path='/'.join(path_segments[:cnt + 1])),
257 class_='pjax-link'))
257 class_='pjax-link'))
258 else:
258 else:
259 url_segments.append(segment_html)
259 url_segments.append(segment_html)
260
260
261 return literal('/'.join(url_segments))
261 return literal('/'.join(url_segments))
262
262
263
263
264 def code_highlight(code, lexer, formatter, use_hl_filter=False):
264 def code_highlight(code, lexer, formatter, use_hl_filter=False):
265 """
265 """
266 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
266 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
267
267
268 If ``outfile`` is given and a valid file object (an object
268 If ``outfile`` is given and a valid file object (an object
269 with a ``write`` method), the result will be written to it, otherwise
269 with a ``write`` method), the result will be written to it, otherwise
270 it is returned as a string.
270 it is returned as a string.
271 """
271 """
272 if use_hl_filter:
272 if use_hl_filter:
273 # add HL filter
273 # add HL filter
274 from rhodecode.lib.index import search_utils
274 from rhodecode.lib.index import search_utils
275 lexer.add_filter(search_utils.ElasticSearchHLFilter())
275 lexer.add_filter(search_utils.ElasticSearchHLFilter())
276 return pygments.format(pygments.lex(code, lexer), formatter)
276 return pygments.format(pygments.lex(code, lexer), formatter)
277
277
278
278
279 class CodeHtmlFormatter(HtmlFormatter):
279 class CodeHtmlFormatter(HtmlFormatter):
280 """
280 """
281 My code Html Formatter for source codes
281 My code Html Formatter for source codes
282 """
282 """
283
283
284 def wrap(self, source, outfile):
284 def wrap(self, source, outfile):
285 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
285 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
286
286
287 def _wrap_code(self, source):
287 def _wrap_code(self, source):
288 for cnt, it in enumerate(source):
288 for cnt, it in enumerate(source):
289 i, t = it
289 i, t = it
290 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
290 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
291 yield i, t
291 yield i, t
292
292
293 def _wrap_tablelinenos(self, inner):
293 def _wrap_tablelinenos(self, inner):
294 dummyoutfile = StringIO.StringIO()
294 dummyoutfile = StringIO.StringIO()
295 lncount = 0
295 lncount = 0
296 for t, line in inner:
296 for t, line in inner:
297 if t:
297 if t:
298 lncount += 1
298 lncount += 1
299 dummyoutfile.write(line)
299 dummyoutfile.write(line)
300
300
301 fl = self.linenostart
301 fl = self.linenostart
302 mw = len(str(lncount + fl - 1))
302 mw = len(str(lncount + fl - 1))
303 sp = self.linenospecial
303 sp = self.linenospecial
304 st = self.linenostep
304 st = self.linenostep
305 la = self.lineanchors
305 la = self.lineanchors
306 aln = self.anchorlinenos
306 aln = self.anchorlinenos
307 nocls = self.noclasses
307 nocls = self.noclasses
308 if sp:
308 if sp:
309 lines = []
309 lines = []
310
310
311 for i in range(fl, fl + lncount):
311 for i in range(fl, fl + lncount):
312 if i % st == 0:
312 if i % st == 0:
313 if i % sp == 0:
313 if i % sp == 0:
314 if aln:
314 if aln:
315 lines.append('<a href="#%s%d" class="special">%*d</a>' %
315 lines.append('<a href="#%s%d" class="special">%*d</a>' %
316 (la, i, mw, i))
316 (la, i, mw, i))
317 else:
317 else:
318 lines.append('<span class="special">%*d</span>' % (mw, i))
318 lines.append('<span class="special">%*d</span>' % (mw, i))
319 else:
319 else:
320 if aln:
320 if aln:
321 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
321 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
322 else:
322 else:
323 lines.append('%*d' % (mw, i))
323 lines.append('%*d' % (mw, i))
324 else:
324 else:
325 lines.append('')
325 lines.append('')
326 ls = '\n'.join(lines)
326 ls = '\n'.join(lines)
327 else:
327 else:
328 lines = []
328 lines = []
329 for i in range(fl, fl + lncount):
329 for i in range(fl, fl + lncount):
330 if i % st == 0:
330 if i % st == 0:
331 if aln:
331 if aln:
332 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
332 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
333 else:
333 else:
334 lines.append('%*d' % (mw, i))
334 lines.append('%*d' % (mw, i))
335 else:
335 else:
336 lines.append('')
336 lines.append('')
337 ls = '\n'.join(lines)
337 ls = '\n'.join(lines)
338
338
339 # in case you wonder about the seemingly redundant <div> here: since the
339 # in case you wonder about the seemingly redundant <div> here: since the
340 # content in the other cell also is wrapped in a div, some browsers in
340 # content in the other cell also is wrapped in a div, some browsers in
341 # some configurations seem to mess up the formatting...
341 # some configurations seem to mess up the formatting...
342 if nocls:
342 if nocls:
343 yield 0, ('<table class="%stable">' % self.cssclass +
343 yield 0, ('<table class="%stable">' % self.cssclass +
344 '<tr><td><div class="linenodiv" '
344 '<tr><td><div class="linenodiv" '
345 'style="background-color: #f0f0f0; padding-right: 10px">'
345 'style="background-color: #f0f0f0; padding-right: 10px">'
346 '<pre style="line-height: 125%">' +
346 '<pre style="line-height: 125%">' +
347 ls + '</pre></div></td><td id="hlcode" class="code">')
347 ls + '</pre></div></td><td id="hlcode" class="code">')
348 else:
348 else:
349 yield 0, ('<table class="%stable">' % self.cssclass +
349 yield 0, ('<table class="%stable">' % self.cssclass +
350 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
350 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
351 ls + '</pre></div></td><td id="hlcode" class="code">')
351 ls + '</pre></div></td><td id="hlcode" class="code">')
352 yield 0, dummyoutfile.getvalue()
352 yield 0, dummyoutfile.getvalue()
353 yield 0, '</td></tr></table>'
353 yield 0, '</td></tr></table>'
354
354
355
355
356 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
356 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
357 def __init__(self, **kw):
357 def __init__(self, **kw):
358 # only show these line numbers if set
358 # only show these line numbers if set
359 self.only_lines = kw.pop('only_line_numbers', [])
359 self.only_lines = kw.pop('only_line_numbers', [])
360 self.query_terms = kw.pop('query_terms', [])
360 self.query_terms = kw.pop('query_terms', [])
361 self.max_lines = kw.pop('max_lines', 5)
361 self.max_lines = kw.pop('max_lines', 5)
362 self.line_context = kw.pop('line_context', 3)
362 self.line_context = kw.pop('line_context', 3)
363 self.url = kw.pop('url', None)
363 self.url = kw.pop('url', None)
364
364
365 super(CodeHtmlFormatter, self).__init__(**kw)
365 super(CodeHtmlFormatter, self).__init__(**kw)
366
366
367 def _wrap_code(self, source):
367 def _wrap_code(self, source):
368 for cnt, it in enumerate(source):
368 for cnt, it in enumerate(source):
369 i, t = it
369 i, t = it
370 t = '<pre>%s</pre>' % t
370 t = '<pre>%s</pre>' % t
371 yield i, t
371 yield i, t
372
372
373 def _wrap_tablelinenos(self, inner):
373 def _wrap_tablelinenos(self, inner):
374 yield 0, '<table class="code-highlight %stable">' % self.cssclass
374 yield 0, '<table class="code-highlight %stable">' % self.cssclass
375
375
376 last_shown_line_number = 0
376 last_shown_line_number = 0
377 current_line_number = 1
377 current_line_number = 1
378
378
379 for t, line in inner:
379 for t, line in inner:
380 if not t:
380 if not t:
381 yield t, line
381 yield t, line
382 continue
382 continue
383
383
384 if current_line_number in self.only_lines:
384 if current_line_number in self.only_lines:
385 if last_shown_line_number + 1 != current_line_number:
385 if last_shown_line_number + 1 != current_line_number:
386 yield 0, '<tr>'
386 yield 0, '<tr>'
387 yield 0, '<td class="line">...</td>'
387 yield 0, '<td class="line">...</td>'
388 yield 0, '<td id="hlcode" class="code"></td>'
388 yield 0, '<td id="hlcode" class="code"></td>'
389 yield 0, '</tr>'
389 yield 0, '</tr>'
390
390
391 yield 0, '<tr>'
391 yield 0, '<tr>'
392 if self.url:
392 if self.url:
393 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
393 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
394 self.url, current_line_number, current_line_number)
394 self.url, current_line_number, current_line_number)
395 else:
395 else:
396 yield 0, '<td class="line"><a href="">%i</a></td>' % (
396 yield 0, '<td class="line"><a href="">%i</a></td>' % (
397 current_line_number)
397 current_line_number)
398 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
398 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
399 yield 0, '</tr>'
399 yield 0, '</tr>'
400
400
401 last_shown_line_number = current_line_number
401 last_shown_line_number = current_line_number
402
402
403 current_line_number += 1
403 current_line_number += 1
404
404
405 yield 0, '</table>'
405 yield 0, '</table>'
406
406
407
407
408 def hsv_to_rgb(h, s, v):
408 def hsv_to_rgb(h, s, v):
409 """ Convert hsv color values to rgb """
409 """ Convert hsv color values to rgb """
410
410
411 if s == 0.0:
411 if s == 0.0:
412 return v, v, v
412 return v, v, v
413 i = int(h * 6.0) # XXX assume int() truncates!
413 i = int(h * 6.0) # XXX assume int() truncates!
414 f = (h * 6.0) - i
414 f = (h * 6.0) - i
415 p = v * (1.0 - s)
415 p = v * (1.0 - s)
416 q = v * (1.0 - s * f)
416 q = v * (1.0 - s * f)
417 t = v * (1.0 - s * (1.0 - f))
417 t = v * (1.0 - s * (1.0 - f))
418 i = i % 6
418 i = i % 6
419 if i == 0:
419 if i == 0:
420 return v, t, p
420 return v, t, p
421 if i == 1:
421 if i == 1:
422 return q, v, p
422 return q, v, p
423 if i == 2:
423 if i == 2:
424 return p, v, t
424 return p, v, t
425 if i == 3:
425 if i == 3:
426 return p, q, v
426 return p, q, v
427 if i == 4:
427 if i == 4:
428 return t, p, v
428 return t, p, v
429 if i == 5:
429 if i == 5:
430 return v, p, q
430 return v, p, q
431
431
432
432
433 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
433 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
434 """
434 """
435 Generator for getting n of evenly distributed colors using
435 Generator for getting n of evenly distributed colors using
436 hsv color and golden ratio. It always return same order of colors
436 hsv color and golden ratio. It always return same order of colors
437
437
438 :param n: number of colors to generate
438 :param n: number of colors to generate
439 :param saturation: saturation of returned colors
439 :param saturation: saturation of returned colors
440 :param lightness: lightness of returned colors
440 :param lightness: lightness of returned colors
441 :returns: RGB tuple
441 :returns: RGB tuple
442 """
442 """
443
443
444 golden_ratio = 0.618033988749895
444 golden_ratio = 0.618033988749895
445 h = 0.22717784590367374
445 h = 0.22717784590367374
446
446
447 for _ in xrange(n):
447 for _ in xrange(n):
448 h += golden_ratio
448 h += golden_ratio
449 h %= 1
449 h %= 1
450 HSV_tuple = [h, saturation, lightness]
450 HSV_tuple = [h, saturation, lightness]
451 RGB_tuple = hsv_to_rgb(*HSV_tuple)
451 RGB_tuple = hsv_to_rgb(*HSV_tuple)
452 yield map(lambda x: str(int(x * 256)), RGB_tuple)
452 yield map(lambda x: str(int(x * 256)), RGB_tuple)
453
453
454
454
455 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
455 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
456 """
456 """
457 Returns a function which when called with an argument returns a unique
457 Returns a function which when called with an argument returns a unique
458 color for that argument, eg.
458 color for that argument, eg.
459
459
460 :param n: number of colors to generate
460 :param n: number of colors to generate
461 :param saturation: saturation of returned colors
461 :param saturation: saturation of returned colors
462 :param lightness: lightness of returned colors
462 :param lightness: lightness of returned colors
463 :returns: css RGB string
463 :returns: css RGB string
464
464
465 >>> color_hash = color_hasher()
465 >>> color_hash = color_hasher()
466 >>> color_hash('hello')
466 >>> color_hash('hello')
467 'rgb(34, 12, 59)'
467 'rgb(34, 12, 59)'
468 >>> color_hash('hello')
468 >>> color_hash('hello')
469 'rgb(34, 12, 59)'
469 'rgb(34, 12, 59)'
470 >>> color_hash('other')
470 >>> color_hash('other')
471 'rgb(90, 224, 159)'
471 'rgb(90, 224, 159)'
472 """
472 """
473
473
474 color_dict = {}
474 color_dict = {}
475 cgenerator = unique_color_generator(
475 cgenerator = unique_color_generator(
476 saturation=saturation, lightness=lightness)
476 saturation=saturation, lightness=lightness)
477
477
478 def get_color_string(thing):
478 def get_color_string(thing):
479 if thing in color_dict:
479 if thing in color_dict:
480 col = color_dict[thing]
480 col = color_dict[thing]
481 else:
481 else:
482 col = color_dict[thing] = cgenerator.next()
482 col = color_dict[thing] = cgenerator.next()
483 return "rgb(%s)" % (', '.join(col))
483 return "rgb(%s)" % (', '.join(col))
484
484
485 return get_color_string
485 return get_color_string
486
486
487
487
488 def get_lexer_safe(mimetype=None, filepath=None):
488 def get_lexer_safe(mimetype=None, filepath=None):
489 """
489 """
490 Tries to return a relevant pygments lexer using mimetype/filepath name,
490 Tries to return a relevant pygments lexer using mimetype/filepath name,
491 defaulting to plain text if none could be found
491 defaulting to plain text if none could be found
492 """
492 """
493 lexer = None
493 lexer = None
494 try:
494 try:
495 if mimetype:
495 if mimetype:
496 lexer = get_lexer_for_mimetype(mimetype)
496 lexer = get_lexer_for_mimetype(mimetype)
497 if not lexer:
497 if not lexer:
498 lexer = get_lexer_for_filename(filepath)
498 lexer = get_lexer_for_filename(filepath)
499 except pygments.util.ClassNotFound:
499 except pygments.util.ClassNotFound:
500 pass
500 pass
501
501
502 if not lexer:
502 if not lexer:
503 lexer = get_lexer_by_name('text')
503 lexer = get_lexer_by_name('text')
504
504
505 return lexer
505 return lexer
506
506
507
507
508 def get_lexer_for_filenode(filenode):
508 def get_lexer_for_filenode(filenode):
509 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
509 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
510 return lexer
510 return lexer
511
511
512
512
513 def pygmentize(filenode, **kwargs):
513 def pygmentize(filenode, **kwargs):
514 """
514 """
515 pygmentize function using pygments
515 pygmentize function using pygments
516
516
517 :param filenode:
517 :param filenode:
518 """
518 """
519 lexer = get_lexer_for_filenode(filenode)
519 lexer = get_lexer_for_filenode(filenode)
520 return literal(code_highlight(filenode.content, lexer,
520 return literal(code_highlight(filenode.content, lexer,
521 CodeHtmlFormatter(**kwargs)))
521 CodeHtmlFormatter(**kwargs)))
522
522
523
523
524 def is_following_repo(repo_name, user_id):
524 def is_following_repo(repo_name, user_id):
525 from rhodecode.model.scm import ScmModel
525 from rhodecode.model.scm import ScmModel
526 return ScmModel().is_following_repo(repo_name, user_id)
526 return ScmModel().is_following_repo(repo_name, user_id)
527
527
528
528
529 class _Message(object):
529 class _Message(object):
530 """A message returned by ``Flash.pop_messages()``.
530 """A message returned by ``Flash.pop_messages()``.
531
531
532 Converting the message to a string returns the message text. Instances
532 Converting the message to a string returns the message text. Instances
533 also have the following attributes:
533 also have the following attributes:
534
534
535 * ``message``: the message text.
535 * ``message``: the message text.
536 * ``category``: the category specified when the message was created.
536 * ``category``: the category specified when the message was created.
537 """
537 """
538
538
539 def __init__(self, category, message):
539 def __init__(self, category, message):
540 self.category = category
540 self.category = category
541 self.message = message
541 self.message = message
542
542
543 def __str__(self):
543 def __str__(self):
544 return self.message
544 return self.message
545
545
546 __unicode__ = __str__
546 __unicode__ = __str__
547
547
548 def __html__(self):
548 def __html__(self):
549 return escape(safe_unicode(self.message))
549 return escape(safe_unicode(self.message))
550
550
551
551
552 class Flash(object):
552 class Flash(object):
553 # List of allowed categories. If None, allow any category.
553 # List of allowed categories. If None, allow any category.
554 categories = ["warning", "notice", "error", "success"]
554 categories = ["warning", "notice", "error", "success"]
555
555
556 # Default category if none is specified.
556 # Default category if none is specified.
557 default_category = "notice"
557 default_category = "notice"
558
558
559 def __init__(self, session_key="flash", categories=None,
559 def __init__(self, session_key="flash", categories=None,
560 default_category=None):
560 default_category=None):
561 """
561 """
562 Instantiate a ``Flash`` object.
562 Instantiate a ``Flash`` object.
563
563
564 ``session_key`` is the key to save the messages under in the user's
564 ``session_key`` is the key to save the messages under in the user's
565 session.
565 session.
566
566
567 ``categories`` is an optional list which overrides the default list
567 ``categories`` is an optional list which overrides the default list
568 of categories.
568 of categories.
569
569
570 ``default_category`` overrides the default category used for messages
570 ``default_category`` overrides the default category used for messages
571 when none is specified.
571 when none is specified.
572 """
572 """
573 self.session_key = session_key
573 self.session_key = session_key
574 if categories is not None:
574 if categories is not None:
575 self.categories = categories
575 self.categories = categories
576 if default_category is not None:
576 if default_category is not None:
577 self.default_category = default_category
577 self.default_category = default_category
578 if self.categories and self.default_category not in self.categories:
578 if self.categories and self.default_category not in self.categories:
579 raise ValueError(
579 raise ValueError(
580 "unrecognized default category %r" % (self.default_category,))
580 "unrecognized default category %r" % (self.default_category,))
581
581
582 def pop_messages(self, session=None, request=None):
582 def pop_messages(self, session=None, request=None):
583 """
583 """
584 Return all accumulated messages and delete them from the session.
584 Return all accumulated messages and delete them from the session.
585
585
586 The return value is a list of ``Message`` objects.
586 The return value is a list of ``Message`` objects.
587 """
587 """
588 messages = []
588 messages = []
589
589
590 if not session:
590 if not session:
591 if not request:
591 if not request:
592 request = get_current_request()
592 request = get_current_request()
593 session = request.session
593 session = request.session
594
594
595 # Pop the 'old' pylons flash messages. They are tuples of the form
595 # Pop the 'old' pylons flash messages. They are tuples of the form
596 # (category, message)
596 # (category, message)
597 for cat, msg in session.pop(self.session_key, []):
597 for cat, msg in session.pop(self.session_key, []):
598 messages.append(_Message(cat, msg))
598 messages.append(_Message(cat, msg))
599
599
600 # Pop the 'new' pyramid flash messages for each category as list
600 # Pop the 'new' pyramid flash messages for each category as list
601 # of strings.
601 # of strings.
602 for cat in self.categories:
602 for cat in self.categories:
603 for msg in session.pop_flash(queue=cat):
603 for msg in session.pop_flash(queue=cat):
604 messages.append(_Message(cat, msg))
604 messages.append(_Message(cat, msg))
605 # Map messages from the default queue to the 'notice' category.
605 # Map messages from the default queue to the 'notice' category.
606 for msg in session.pop_flash():
606 for msg in session.pop_flash():
607 messages.append(_Message('notice', msg))
607 messages.append(_Message('notice', msg))
608
608
609 session.save()
609 session.save()
610 return messages
610 return messages
611
611
612 def json_alerts(self, session=None, request=None):
612 def json_alerts(self, session=None, request=None):
613 payloads = []
613 payloads = []
614 messages = flash.pop_messages(session=session, request=request)
614 messages = flash.pop_messages(session=session, request=request)
615 if messages:
615 if messages:
616 for message in messages:
616 for message in messages:
617 subdata = {}
617 subdata = {}
618 if hasattr(message.message, 'rsplit'):
618 if hasattr(message.message, 'rsplit'):
619 flash_data = message.message.rsplit('|DELIM|', 1)
619 flash_data = message.message.rsplit('|DELIM|', 1)
620 org_message = flash_data[0]
620 org_message = flash_data[0]
621 if len(flash_data) > 1:
621 if len(flash_data) > 1:
622 subdata = json.loads(flash_data[1])
622 subdata = json.loads(flash_data[1])
623 else:
623 else:
624 org_message = message.message
624 org_message = message.message
625 payloads.append({
625 payloads.append({
626 'message': {
626 'message': {
627 'message': u'{}'.format(org_message),
627 'message': u'{}'.format(org_message),
628 'level': message.category,
628 'level': message.category,
629 'force': True,
629 'force': True,
630 'subdata': subdata
630 'subdata': subdata
631 }
631 }
632 })
632 })
633 return json.dumps(payloads)
633 return json.dumps(payloads)
634
634
635 def __call__(self, message, category=None, ignore_duplicate=False,
635 def __call__(self, message, category=None, ignore_duplicate=False,
636 session=None, request=None):
636 session=None, request=None):
637
637
638 if not session:
638 if not session:
639 if not request:
639 if not request:
640 request = get_current_request()
640 request = get_current_request()
641 session = request.session
641 session = request.session
642
642
643 session.flash(
643 session.flash(
644 message, queue=category, allow_duplicate=not ignore_duplicate)
644 message, queue=category, allow_duplicate=not ignore_duplicate)
645
645
646
646
647 flash = Flash()
647 flash = Flash()
648
648
649 #==============================================================================
649 #==============================================================================
650 # SCM FILTERS available via h.
650 # SCM FILTERS available via h.
651 #==============================================================================
651 #==============================================================================
652 from rhodecode.lib.vcs.utils import author_name, author_email
652 from rhodecode.lib.vcs.utils import author_name, author_email
653 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
653 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
654 from rhodecode.model.db import User, ChangesetStatus
654 from rhodecode.model.db import User, ChangesetStatus
655
655
656 capitalize = lambda x: x.capitalize()
656 capitalize = lambda x: x.capitalize()
657 email = author_email
657 email = author_email
658 short_id = lambda x: x[:12]
658 short_id = lambda x: x[:12]
659 hide_credentials = lambda x: ''.join(credentials_filter(x))
659 hide_credentials = lambda x: ''.join(credentials_filter(x))
660
660
661
661
662 import pytz
662 import pytz
663 import tzlocal
663 import tzlocal
664 local_timezone = tzlocal.get_localzone()
664 local_timezone = tzlocal.get_localzone()
665
665
666
666
667 def age_component(datetime_iso, value=None, time_is_local=False):
667 def age_component(datetime_iso, value=None, time_is_local=False):
668 title = value or format_date(datetime_iso)
668 title = value or format_date(datetime_iso)
669 tzinfo = '+00:00'
669 tzinfo = '+00:00'
670
670
671 # detect if we have a timezone info, otherwise, add it
671 # detect if we have a timezone info, otherwise, add it
672 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
672 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
673 force_timezone = os.environ.get('RC_TIMEZONE', '')
673 force_timezone = os.environ.get('RC_TIMEZONE', '')
674 if force_timezone:
674 if force_timezone:
675 force_timezone = pytz.timezone(force_timezone)
675 force_timezone = pytz.timezone(force_timezone)
676 timezone = force_timezone or local_timezone
676 timezone = force_timezone or local_timezone
677 offset = timezone.localize(datetime_iso).strftime('%z')
677 offset = timezone.localize(datetime_iso).strftime('%z')
678 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
678 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
679
679
680 return literal(
680 return literal(
681 '<time class="timeago tooltip" '
681 '<time class="timeago tooltip" '
682 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
682 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
683 datetime_iso, title, tzinfo))
683 datetime_iso, title, tzinfo))
684
684
685
685
686 def _shorten_commit_id(commit_id, commit_len=None):
686 def _shorten_commit_id(commit_id, commit_len=None):
687 if commit_len is None:
687 if commit_len is None:
688 request = get_current_request()
688 request = get_current_request()
689 commit_len = request.call_context.visual.show_sha_length
689 commit_len = request.call_context.visual.show_sha_length
690 return commit_id[:commit_len]
690 return commit_id[:commit_len]
691
691
692
692
693 def show_id(commit, show_idx=None, commit_len=None):
693 def show_id(commit, show_idx=None, commit_len=None):
694 """
694 """
695 Configurable function that shows ID
695 Configurable function that shows ID
696 by default it's r123:fffeeefffeee
696 by default it's r123:fffeeefffeee
697
697
698 :param commit: commit instance
698 :param commit: commit instance
699 """
699 """
700 if show_idx is None:
700 if show_idx is None:
701 request = get_current_request()
701 request = get_current_request()
702 show_idx = request.call_context.visual.show_revision_number
702 show_idx = request.call_context.visual.show_revision_number
703
703
704 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
704 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
705 if show_idx:
705 if show_idx:
706 return 'r%s:%s' % (commit.idx, raw_id)
706 return 'r%s:%s' % (commit.idx, raw_id)
707 else:
707 else:
708 return '%s' % (raw_id, )
708 return '%s' % (raw_id, )
709
709
710
710
711 def format_date(date):
711 def format_date(date):
712 """
712 """
713 use a standardized formatting for dates used in RhodeCode
713 use a standardized formatting for dates used in RhodeCode
714
714
715 :param date: date/datetime object
715 :param date: date/datetime object
716 :return: formatted date
716 :return: formatted date
717 """
717 """
718
718
719 if date:
719 if date:
720 _fmt = "%a, %d %b %Y %H:%M:%S"
720 _fmt = "%a, %d %b %Y %H:%M:%S"
721 return safe_unicode(date.strftime(_fmt))
721 return safe_unicode(date.strftime(_fmt))
722
722
723 return u""
723 return u""
724
724
725
725
726 class _RepoChecker(object):
726 class _RepoChecker(object):
727
727
728 def __init__(self, backend_alias):
728 def __init__(self, backend_alias):
729 self._backend_alias = backend_alias
729 self._backend_alias = backend_alias
730
730
731 def __call__(self, repository):
731 def __call__(self, repository):
732 if hasattr(repository, 'alias'):
732 if hasattr(repository, 'alias'):
733 _type = repository.alias
733 _type = repository.alias
734 elif hasattr(repository, 'repo_type'):
734 elif hasattr(repository, 'repo_type'):
735 _type = repository.repo_type
735 _type = repository.repo_type
736 else:
736 else:
737 _type = repository
737 _type = repository
738 return _type == self._backend_alias
738 return _type == self._backend_alias
739
739
740
740
741 is_git = _RepoChecker('git')
741 is_git = _RepoChecker('git')
742 is_hg = _RepoChecker('hg')
742 is_hg = _RepoChecker('hg')
743 is_svn = _RepoChecker('svn')
743 is_svn = _RepoChecker('svn')
744
744
745
745
746 def get_repo_type_by_name(repo_name):
746 def get_repo_type_by_name(repo_name):
747 repo = Repository.get_by_repo_name(repo_name)
747 repo = Repository.get_by_repo_name(repo_name)
748 if repo:
748 if repo:
749 return repo.repo_type
749 return repo.repo_type
750
750
751
751
752 def is_svn_without_proxy(repository):
752 def is_svn_without_proxy(repository):
753 if is_svn(repository):
753 if is_svn(repository):
754 from rhodecode.model.settings import VcsSettingsModel
754 from rhodecode.model.settings import VcsSettingsModel
755 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
755 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
756 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
756 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
757 return False
757 return False
758
758
759
759
760 def discover_user(author):
760 def discover_user(author):
761 """
761 """
762 Tries to discover RhodeCode User based on the autho string. Author string
762 Tries to discover RhodeCode User based on the autho string. Author string
763 is typically `FirstName LastName <email@address.com>`
763 is typically `FirstName LastName <email@address.com>`
764 """
764 """
765
765
766 # if author is already an instance use it for extraction
766 # if author is already an instance use it for extraction
767 if isinstance(author, User):
767 if isinstance(author, User):
768 return author
768 return author
769
769
770 # Valid email in the attribute passed, see if they're in the system
770 # Valid email in the attribute passed, see if they're in the system
771 _email = author_email(author)
771 _email = author_email(author)
772 if _email != '':
772 if _email != '':
773 user = User.get_by_email(_email, case_insensitive=True, cache=True)
773 user = User.get_by_email(_email, case_insensitive=True, cache=True)
774 if user is not None:
774 if user is not None:
775 return user
775 return user
776
776
777 # Maybe it's a username, we try to extract it and fetch by username ?
777 # Maybe it's a username, we try to extract it and fetch by username ?
778 _author = author_name(author)
778 _author = author_name(author)
779 user = User.get_by_username(_author, case_insensitive=True, cache=True)
779 user = User.get_by_username(_author, case_insensitive=True, cache=True)
780 if user is not None:
780 if user is not None:
781 return user
781 return user
782
782
783 return None
783 return None
784
784
785
785
786 def email_or_none(author):
786 def email_or_none(author):
787 # extract email from the commit string
787 # extract email from the commit string
788 _email = author_email(author)
788 _email = author_email(author)
789
789
790 # If we have an email, use it, otherwise
790 # If we have an email, use it, otherwise
791 # see if it contains a username we can get an email from
791 # see if it contains a username we can get an email from
792 if _email != '':
792 if _email != '':
793 return _email
793 return _email
794 else:
794 else:
795 user = User.get_by_username(
795 user = User.get_by_username(
796 author_name(author), case_insensitive=True, cache=True)
796 author_name(author), case_insensitive=True, cache=True)
797
797
798 if user is not None:
798 if user is not None:
799 return user.email
799 return user.email
800
800
801 # No valid email, not a valid user in the system, none!
801 # No valid email, not a valid user in the system, none!
802 return None
802 return None
803
803
804
804
805 def link_to_user(author, length=0, **kwargs):
805 def link_to_user(author, length=0, **kwargs):
806 user = discover_user(author)
806 user = discover_user(author)
807 # user can be None, but if we have it already it means we can re-use it
807 # user can be None, but if we have it already it means we can re-use it
808 # in the person() function, so we save 1 intensive-query
808 # in the person() function, so we save 1 intensive-query
809 if user:
809 if user:
810 author = user
810 author = user
811
811
812 display_person = person(author, 'username_or_name_or_email')
812 display_person = person(author, 'username_or_name_or_email')
813 if length:
813 if length:
814 display_person = shorter(display_person, length)
814 display_person = shorter(display_person, length)
815
815
816 if user:
816 if user:
817 return link_to(
817 return link_to(
818 escape(display_person),
818 escape(display_person),
819 route_path('user_profile', username=user.username),
819 route_path('user_profile', username=user.username),
820 **kwargs)
820 **kwargs)
821 else:
821 else:
822 return escape(display_person)
822 return escape(display_person)
823
823
824
824
825 def link_to_group(users_group_name, **kwargs):
825 def link_to_group(users_group_name, **kwargs):
826 return link_to(
826 return link_to(
827 escape(users_group_name),
827 escape(users_group_name),
828 route_path('user_group_profile', user_group_name=users_group_name),
828 route_path('user_group_profile', user_group_name=users_group_name),
829 **kwargs)
829 **kwargs)
830
830
831
831
832 def person(author, show_attr="username_and_name"):
832 def person(author, show_attr="username_and_name"):
833 user = discover_user(author)
833 user = discover_user(author)
834 if user:
834 if user:
835 return getattr(user, show_attr)
835 return getattr(user, show_attr)
836 else:
836 else:
837 _author = author_name(author)
837 _author = author_name(author)
838 _email = email(author)
838 _email = email(author)
839 return _author or _email
839 return _author or _email
840
840
841
841
842 def author_string(email):
842 def author_string(email):
843 if email:
843 if email:
844 user = User.get_by_email(email, case_insensitive=True, cache=True)
844 user = User.get_by_email(email, case_insensitive=True, cache=True)
845 if user:
845 if user:
846 if user.first_name or user.last_name:
846 if user.first_name or user.last_name:
847 return '%s %s &lt;%s&gt;' % (
847 return '%s %s &lt;%s&gt;' % (
848 user.first_name, user.last_name, email)
848 user.first_name, user.last_name, email)
849 else:
849 else:
850 return email
850 return email
851 else:
851 else:
852 return email
852 return email
853 else:
853 else:
854 return None
854 return None
855
855
856
856
857 def person_by_id(id_, show_attr="username_and_name"):
857 def person_by_id(id_, show_attr="username_and_name"):
858 # attr to return from fetched user
858 # attr to return from fetched user
859 person_getter = lambda usr: getattr(usr, show_attr)
859 person_getter = lambda usr: getattr(usr, show_attr)
860
860
861 #maybe it's an ID ?
861 #maybe it's an ID ?
862 if str(id_).isdigit() or isinstance(id_, int):
862 if str(id_).isdigit() or isinstance(id_, int):
863 id_ = int(id_)
863 id_ = int(id_)
864 user = User.get(id_)
864 user = User.get(id_)
865 if user is not None:
865 if user is not None:
866 return person_getter(user)
866 return person_getter(user)
867 return id_
867 return id_
868
868
869
869
870 def gravatar_with_user(request, author, show_disabled=False):
870 def gravatar_with_user(request, author, show_disabled=False):
871 _render = request.get_partial_renderer(
871 _render = request.get_partial_renderer(
872 'rhodecode:templates/base/base.mako')
872 'rhodecode:templates/base/base.mako')
873 return _render('gravatar_with_user', author, show_disabled=show_disabled)
873 return _render('gravatar_with_user', author, show_disabled=show_disabled)
874
874
875
875
876 tags_paterns = OrderedDict((
876 tags_paterns = OrderedDict((
877 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
877 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
878 '<div class="metatag" tag="lang">\\2</div>')),
878 '<div class="metatag" tag="lang">\\2</div>')),
879
879
880 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
880 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
881 '<div class="metatag" tag="see">see: \\1 </div>')),
881 '<div class="metatag" tag="see">see: \\1 </div>')),
882
882
883 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
883 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
884 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
884 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
885
885
886 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
886 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
887 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
887 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
888
888
889 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
889 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
890 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
890 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
891
891
892 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
892 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
893 '<div class="metatag" tag="state \\1">\\1</div>')),
893 '<div class="metatag" tag="state \\1">\\1</div>')),
894
894
895 # label in grey
895 # label in grey
896 ('label', (re.compile(r'\[([a-z]+)\]'),
896 ('label', (re.compile(r'\[([a-z]+)\]'),
897 '<div class="metatag" tag="label">\\1</div>')),
897 '<div class="metatag" tag="label">\\1</div>')),
898
898
899 # generic catch all in grey
899 # generic catch all in grey
900 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
900 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
901 '<div class="metatag" tag="generic">\\1</div>')),
901 '<div class="metatag" tag="generic">\\1</div>')),
902 ))
902 ))
903
903
904
904
905 def extract_metatags(value):
905 def extract_metatags(value):
906 """
906 """
907 Extract supported meta-tags from given text value
907 Extract supported meta-tags from given text value
908 """
908 """
909 tags = []
909 tags = []
910 if not value:
910 if not value:
911 return tags, ''
911 return tags, ''
912
912
913 for key, val in tags_paterns.items():
913 for key, val in tags_paterns.items():
914 pat, replace_html = val
914 pat, replace_html = val
915 tags.extend([(key, x.group()) for x in pat.finditer(value)])
915 tags.extend([(key, x.group()) for x in pat.finditer(value)])
916 value = pat.sub('', value)
916 value = pat.sub('', value)
917
917
918 return tags, value
918 return tags, value
919
919
920
920
921 def style_metatag(tag_type, value):
921 def style_metatag(tag_type, value):
922 """
922 """
923 converts tags from value into html equivalent
923 converts tags from value into html equivalent
924 """
924 """
925 if not value:
925 if not value:
926 return ''
926 return ''
927
927
928 html_value = value
928 html_value = value
929 tag_data = tags_paterns.get(tag_type)
929 tag_data = tags_paterns.get(tag_type)
930 if tag_data:
930 if tag_data:
931 pat, replace_html = tag_data
931 pat, replace_html = tag_data
932 # convert to plain `unicode` instead of a markup tag to be used in
932 # convert to plain `unicode` instead of a markup tag to be used in
933 # regex expressions. safe_unicode doesn't work here
933 # regex expressions. safe_unicode doesn't work here
934 html_value = pat.sub(replace_html, unicode(value))
934 html_value = pat.sub(replace_html, unicode(value))
935
935
936 return html_value
936 return html_value
937
937
938
938
939 def bool2icon(value, show_at_false=True):
939 def bool2icon(value, show_at_false=True):
940 """
940 """
941 Returns boolean value of a given value, represented as html element with
941 Returns boolean value of a given value, represented as html element with
942 classes that will represent icons
942 classes that will represent icons
943
943
944 :param value: given value to convert to html node
944 :param value: given value to convert to html node
945 """
945 """
946
946
947 if value: # does bool conversion
947 if value: # does bool conversion
948 return HTML.tag('i', class_="icon-true")
948 return HTML.tag('i', class_="icon-true")
949 else: # not true as bool
949 else: # not true as bool
950 if show_at_false:
950 if show_at_false:
951 return HTML.tag('i', class_="icon-false")
951 return HTML.tag('i', class_="icon-false")
952 return HTML.tag('i')
952 return HTML.tag('i')
953
953
954 #==============================================================================
954 #==============================================================================
955 # PERMS
955 # PERMS
956 #==============================================================================
956 #==============================================================================
957 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
957 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
958 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
958 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
959 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
959 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
960 csrf_token_key
960 csrf_token_key
961
961
962
962
963 #==============================================================================
963 #==============================================================================
964 # GRAVATAR URL
964 # GRAVATAR URL
965 #==============================================================================
965 #==============================================================================
966 class InitialsGravatar(object):
966 class InitialsGravatar(object):
967 def __init__(self, email_address, first_name, last_name, size=30,
967 def __init__(self, email_address, first_name, last_name, size=30,
968 background=None, text_color='#fff'):
968 background=None, text_color='#fff'):
969 self.size = size
969 self.size = size
970 self.first_name = first_name
970 self.first_name = first_name
971 self.last_name = last_name
971 self.last_name = last_name
972 self.email_address = email_address
972 self.email_address = email_address
973 self.background = background or self.str2color(email_address)
973 self.background = background or self.str2color(email_address)
974 self.text_color = text_color
974 self.text_color = text_color
975
975
976 def get_color_bank(self):
976 def get_color_bank(self):
977 """
977 """
978 returns a predefined list of colors that gravatars can use.
978 returns a predefined list of colors that gravatars can use.
979 Those are randomized distinct colors that guarantee readability and
979 Those are randomized distinct colors that guarantee readability and
980 uniqueness.
980 uniqueness.
981
981
982 generated with: http://phrogz.net/css/distinct-colors.html
982 generated with: http://phrogz.net/css/distinct-colors.html
983 """
983 """
984 return [
984 return [
985 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
985 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
986 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
986 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
987 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
987 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
988 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
988 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
989 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
989 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
990 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
990 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
991 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
991 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
992 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
992 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
993 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
993 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
994 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
994 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
995 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
995 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
996 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
996 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
997 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
997 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
998 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
998 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
999 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
999 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1000 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1000 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1001 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1001 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1002 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1002 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1003 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1003 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1004 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1004 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1005 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1005 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1006 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1006 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1007 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1007 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1008 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1008 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1009 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1009 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1010 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1010 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1011 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1011 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1012 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1012 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1013 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1013 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1014 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1014 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1015 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1015 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1016 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1016 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1017 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1017 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1018 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1018 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1019 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1019 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1020 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1020 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1021 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1021 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1022 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1022 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1023 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1023 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1024 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1024 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1025 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1025 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1026 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1026 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1027 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1027 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1028 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1028 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1029 '#4f8c46', '#368dd9', '#5c0073'
1029 '#4f8c46', '#368dd9', '#5c0073'
1030 ]
1030 ]
1031
1031
1032 def rgb_to_hex_color(self, rgb_tuple):
1032 def rgb_to_hex_color(self, rgb_tuple):
1033 """
1033 """
1034 Converts an rgb_tuple passed to an hex color.
1034 Converts an rgb_tuple passed to an hex color.
1035
1035
1036 :param rgb_tuple: tuple with 3 ints represents rgb color space
1036 :param rgb_tuple: tuple with 3 ints represents rgb color space
1037 """
1037 """
1038 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1038 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1039
1039
1040 def email_to_int_list(self, email_str):
1040 def email_to_int_list(self, email_str):
1041 """
1041 """
1042 Get every byte of the hex digest value of email and turn it to integer.
1042 Get every byte of the hex digest value of email and turn it to integer.
1043 It's going to be always between 0-255
1043 It's going to be always between 0-255
1044 """
1044 """
1045 digest = md5_safe(email_str.lower())
1045 digest = md5_safe(email_str.lower())
1046 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1046 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1047
1047
1048 def pick_color_bank_index(self, email_str, color_bank):
1048 def pick_color_bank_index(self, email_str, color_bank):
1049 return self.email_to_int_list(email_str)[0] % len(color_bank)
1049 return self.email_to_int_list(email_str)[0] % len(color_bank)
1050
1050
1051 def str2color(self, email_str):
1051 def str2color(self, email_str):
1052 """
1052 """
1053 Tries to map in a stable algorithm an email to color
1053 Tries to map in a stable algorithm an email to color
1054
1054
1055 :param email_str:
1055 :param email_str:
1056 """
1056 """
1057 color_bank = self.get_color_bank()
1057 color_bank = self.get_color_bank()
1058 # pick position (module it's length so we always find it in the
1058 # pick position (module it's length so we always find it in the
1059 # bank even if it's smaller than 256 values
1059 # bank even if it's smaller than 256 values
1060 pos = self.pick_color_bank_index(email_str, color_bank)
1060 pos = self.pick_color_bank_index(email_str, color_bank)
1061 return color_bank[pos]
1061 return color_bank[pos]
1062
1062
1063 def normalize_email(self, email_address):
1063 def normalize_email(self, email_address):
1064 import unicodedata
1064 import unicodedata
1065 # default host used to fill in the fake/missing email
1065 # default host used to fill in the fake/missing email
1066 default_host = u'localhost'
1066 default_host = u'localhost'
1067
1067
1068 if not email_address:
1068 if not email_address:
1069 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1069 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1070
1070
1071 email_address = safe_unicode(email_address)
1071 email_address = safe_unicode(email_address)
1072
1072
1073 if u'@' not in email_address:
1073 if u'@' not in email_address:
1074 email_address = u'%s@%s' % (email_address, default_host)
1074 email_address = u'%s@%s' % (email_address, default_host)
1075
1075
1076 if email_address.endswith(u'@'):
1076 if email_address.endswith(u'@'):
1077 email_address = u'%s%s' % (email_address, default_host)
1077 email_address = u'%s%s' % (email_address, default_host)
1078
1078
1079 email_address = unicodedata.normalize('NFKD', email_address)\
1079 email_address = unicodedata.normalize('NFKD', email_address)\
1080 .encode('ascii', 'ignore')
1080 .encode('ascii', 'ignore')
1081 return email_address
1081 return email_address
1082
1082
1083 def get_initials(self):
1083 def get_initials(self):
1084 """
1084 """
1085 Returns 2 letter initials calculated based on the input.
1085 Returns 2 letter initials calculated based on the input.
1086 The algorithm picks first given email address, and takes first letter
1086 The algorithm picks first given email address, and takes first letter
1087 of part before @, and then the first letter of server name. In case
1087 of part before @, and then the first letter of server name. In case
1088 the part before @ is in a format of `somestring.somestring2` it replaces
1088 the part before @ is in a format of `somestring.somestring2` it replaces
1089 the server letter with first letter of somestring2
1089 the server letter with first letter of somestring2
1090
1090
1091 In case function was initialized with both first and lastname, this
1091 In case function was initialized with both first and lastname, this
1092 overrides the extraction from email by first letter of the first and
1092 overrides the extraction from email by first letter of the first and
1093 last name. We add special logic to that functionality, In case Full name
1093 last name. We add special logic to that functionality, In case Full name
1094 is compound, like Guido Von Rossum, we use last part of the last name
1094 is compound, like Guido Von Rossum, we use last part of the last name
1095 (Von Rossum) picking `R`.
1095 (Von Rossum) picking `R`.
1096
1096
1097 Function also normalizes the non-ascii characters to they ascii
1097 Function also normalizes the non-ascii characters to they ascii
1098 representation, eg Δ„ => A
1098 representation, eg Δ„ => A
1099 """
1099 """
1100 import unicodedata
1100 import unicodedata
1101 # replace non-ascii to ascii
1101 # replace non-ascii to ascii
1102 first_name = unicodedata.normalize(
1102 first_name = unicodedata.normalize(
1103 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1103 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1104 last_name = unicodedata.normalize(
1104 last_name = unicodedata.normalize(
1105 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1105 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1106
1106
1107 # do NFKD encoding, and also make sure email has proper format
1107 # do NFKD encoding, and also make sure email has proper format
1108 email_address = self.normalize_email(self.email_address)
1108 email_address = self.normalize_email(self.email_address)
1109
1109
1110 # first push the email initials
1110 # first push the email initials
1111 prefix, server = email_address.split('@', 1)
1111 prefix, server = email_address.split('@', 1)
1112
1112
1113 # check if prefix is maybe a 'first_name.last_name' syntax
1113 # check if prefix is maybe a 'first_name.last_name' syntax
1114 _dot_split = prefix.rsplit('.', 1)
1114 _dot_split = prefix.rsplit('.', 1)
1115 if len(_dot_split) == 2 and _dot_split[1]:
1115 if len(_dot_split) == 2 and _dot_split[1]:
1116 initials = [_dot_split[0][0], _dot_split[1][0]]
1116 initials = [_dot_split[0][0], _dot_split[1][0]]
1117 else:
1117 else:
1118 initials = [prefix[0], server[0]]
1118 initials = [prefix[0], server[0]]
1119
1119
1120 # then try to replace either first_name or last_name
1120 # then try to replace either first_name or last_name
1121 fn_letter = (first_name or " ")[0].strip()
1121 fn_letter = (first_name or " ")[0].strip()
1122 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1122 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1123
1123
1124 if fn_letter:
1124 if fn_letter:
1125 initials[0] = fn_letter
1125 initials[0] = fn_letter
1126
1126
1127 if ln_letter:
1127 if ln_letter:
1128 initials[1] = ln_letter
1128 initials[1] = ln_letter
1129
1129
1130 return ''.join(initials).upper()
1130 return ''.join(initials).upper()
1131
1131
1132 def get_img_data_by_type(self, font_family, img_type):
1132 def get_img_data_by_type(self, font_family, img_type):
1133 default_user = """
1133 default_user = """
1134 <svg xmlns="http://www.w3.org/2000/svg"
1134 <svg xmlns="http://www.w3.org/2000/svg"
1135 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1135 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1136 viewBox="-15 -10 439.165 429.164"
1136 viewBox="-15 -10 439.165 429.164"
1137
1137
1138 xml:space="preserve"
1138 xml:space="preserve"
1139 style="background:{background};" >
1139 style="background:{background};" >
1140
1140
1141 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1141 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1142 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1142 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1143 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1143 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1144 168.596,153.916,216.671,
1144 168.596,153.916,216.671,
1145 204.583,216.671z" fill="{text_color}"/>
1145 204.583,216.671z" fill="{text_color}"/>
1146 <path d="M407.164,374.717L360.88,
1146 <path d="M407.164,374.717L360.88,
1147 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1147 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1148 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1148 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1149 15.366-44.203,23.488-69.076,23.488c-24.877,
1149 15.366-44.203,23.488-69.076,23.488c-24.877,
1150 0-48.762-8.122-69.078-23.488
1150 0-48.762-8.122-69.078-23.488
1151 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1151 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1152 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1152 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1153 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1153 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1154 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1154 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1155 19.402-10.527 C409.699,390.129,
1155 19.402-10.527 C409.699,390.129,
1156 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1156 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1157 </svg>""".format(
1157 </svg>""".format(
1158 size=self.size,
1158 size=self.size,
1159 background='#979797', # @grey4
1159 background='#979797', # @grey4
1160 text_color=self.text_color,
1160 text_color=self.text_color,
1161 font_family=font_family)
1161 font_family=font_family)
1162
1162
1163 return {
1163 return {
1164 "default_user": default_user
1164 "default_user": default_user
1165 }[img_type]
1165 }[img_type]
1166
1166
1167 def get_img_data(self, svg_type=None):
1167 def get_img_data(self, svg_type=None):
1168 """
1168 """
1169 generates the svg metadata for image
1169 generates the svg metadata for image
1170 """
1170 """
1171 fonts = [
1171 fonts = [
1172 '-apple-system',
1172 '-apple-system',
1173 'BlinkMacSystemFont',
1173 'BlinkMacSystemFont',
1174 'Segoe UI',
1174 'Segoe UI',
1175 'Roboto',
1175 'Roboto',
1176 'Oxygen-Sans',
1176 'Oxygen-Sans',
1177 'Ubuntu',
1177 'Ubuntu',
1178 'Cantarell',
1178 'Cantarell',
1179 'Helvetica Neue',
1179 'Helvetica Neue',
1180 'sans-serif'
1180 'sans-serif'
1181 ]
1181 ]
1182 font_family = ','.join(fonts)
1182 font_family = ','.join(fonts)
1183 if svg_type:
1183 if svg_type:
1184 return self.get_img_data_by_type(font_family, svg_type)
1184 return self.get_img_data_by_type(font_family, svg_type)
1185
1185
1186 initials = self.get_initials()
1186 initials = self.get_initials()
1187 img_data = """
1187 img_data = """
1188 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1188 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1189 width="{size}" height="{size}"
1189 width="{size}" height="{size}"
1190 style="width: 100%; height: 100%; background-color: {background}"
1190 style="width: 100%; height: 100%; background-color: {background}"
1191 viewBox="0 0 {size} {size}">
1191 viewBox="0 0 {size} {size}">
1192 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1192 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1193 pointer-events="auto" fill="{text_color}"
1193 pointer-events="auto" fill="{text_color}"
1194 font-family="{font_family}"
1194 font-family="{font_family}"
1195 style="font-weight: 400; font-size: {f_size}px;">{text}
1195 style="font-weight: 400; font-size: {f_size}px;">{text}
1196 </text>
1196 </text>
1197 </svg>""".format(
1197 </svg>""".format(
1198 size=self.size,
1198 size=self.size,
1199 f_size=self.size/1.85, # scale the text inside the box nicely
1199 f_size=self.size/1.85, # scale the text inside the box nicely
1200 background=self.background,
1200 background=self.background,
1201 text_color=self.text_color,
1201 text_color=self.text_color,
1202 text=initials.upper(),
1202 text=initials.upper(),
1203 font_family=font_family)
1203 font_family=font_family)
1204
1204
1205 return img_data
1205 return img_data
1206
1206
1207 def generate_svg(self, svg_type=None):
1207 def generate_svg(self, svg_type=None):
1208 img_data = self.get_img_data(svg_type)
1208 img_data = self.get_img_data(svg_type)
1209 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1209 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1210
1210
1211
1211
1212 def initials_gravatar(email_address, first_name, last_name, size=30):
1212 def initials_gravatar(email_address, first_name, last_name, size=30):
1213 svg_type = None
1213 svg_type = None
1214 if email_address == User.DEFAULT_USER_EMAIL:
1214 if email_address == User.DEFAULT_USER_EMAIL:
1215 svg_type = 'default_user'
1215 svg_type = 'default_user'
1216 klass = InitialsGravatar(email_address, first_name, last_name, size)
1216 klass = InitialsGravatar(email_address, first_name, last_name, size)
1217 return klass.generate_svg(svg_type=svg_type)
1217 return klass.generate_svg(svg_type=svg_type)
1218
1218
1219
1219
1220 def gravatar_url(email_address, size=30, request=None):
1220 def gravatar_url(email_address, size=30, request=None):
1221 request = get_current_request()
1221 request = get_current_request()
1222 _use_gravatar = request.call_context.visual.use_gravatar
1222 _use_gravatar = request.call_context.visual.use_gravatar
1223 _gravatar_url = request.call_context.visual.gravatar_url
1223 _gravatar_url = request.call_context.visual.gravatar_url
1224
1224
1225 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1225 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1226
1226
1227 email_address = email_address or User.DEFAULT_USER_EMAIL
1227 email_address = email_address or User.DEFAULT_USER_EMAIL
1228 if isinstance(email_address, unicode):
1228 if isinstance(email_address, unicode):
1229 # hashlib crashes on unicode items
1229 # hashlib crashes on unicode items
1230 email_address = safe_str(email_address)
1230 email_address = safe_str(email_address)
1231
1231
1232 # empty email or default user
1232 # empty email or default user
1233 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1233 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1234 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1234 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1235
1235
1236 if _use_gravatar:
1236 if _use_gravatar:
1237 # TODO: Disuse pyramid thread locals. Think about another solution to
1237 # TODO: Disuse pyramid thread locals. Think about another solution to
1238 # get the host and schema here.
1238 # get the host and schema here.
1239 request = get_current_request()
1239 request = get_current_request()
1240 tmpl = safe_str(_gravatar_url)
1240 tmpl = safe_str(_gravatar_url)
1241 tmpl = tmpl.replace('{email}', email_address)\
1241 tmpl = tmpl.replace('{email}', email_address)\
1242 .replace('{md5email}', md5_safe(email_address.lower())) \
1242 .replace('{md5email}', md5_safe(email_address.lower())) \
1243 .replace('{netloc}', request.host)\
1243 .replace('{netloc}', request.host)\
1244 .replace('{scheme}', request.scheme)\
1244 .replace('{scheme}', request.scheme)\
1245 .replace('{size}', safe_str(size))
1245 .replace('{size}', safe_str(size))
1246 return tmpl
1246 return tmpl
1247 else:
1247 else:
1248 return initials_gravatar(email_address, '', '', size=size)
1248 return initials_gravatar(email_address, '', '', size=size)
1249
1249
1250
1250
1251 class Page(_Page):
1251 class Page(_Page):
1252 """
1252 """
1253 Custom pager to match rendering style with paginator
1253 Custom pager to match rendering style with paginator
1254 """
1254 """
1255
1255
1256 def _get_pos(self, cur_page, max_page, items):
1256 def _get_pos(self, cur_page, max_page, items):
1257 edge = (items / 2) + 1
1257 edge = (items / 2) + 1
1258 if (cur_page <= edge):
1258 if (cur_page <= edge):
1259 radius = max(items / 2, items - cur_page)
1259 radius = max(items / 2, items - cur_page)
1260 elif (max_page - cur_page) < edge:
1260 elif (max_page - cur_page) < edge:
1261 radius = (items - 1) - (max_page - cur_page)
1261 radius = (items - 1) - (max_page - cur_page)
1262 else:
1262 else:
1263 radius = items / 2
1263 radius = items / 2
1264
1264
1265 left = max(1, (cur_page - (radius)))
1265 left = max(1, (cur_page - (radius)))
1266 right = min(max_page, cur_page + (radius))
1266 right = min(max_page, cur_page + (radius))
1267 return left, cur_page, right
1267 return left, cur_page, right
1268
1268
1269 def _range(self, regexp_match):
1269 def _range(self, regexp_match):
1270 """
1270 """
1271 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1271 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1272
1272
1273 Arguments:
1273 Arguments:
1274
1274
1275 regexp_match
1275 regexp_match
1276 A "re" (regular expressions) match object containing the
1276 A "re" (regular expressions) match object containing the
1277 radius of linked pages around the current page in
1277 radius of linked pages around the current page in
1278 regexp_match.group(1) as a string
1278 regexp_match.group(1) as a string
1279
1279
1280 This function is supposed to be called as a callable in
1280 This function is supposed to be called as a callable in
1281 re.sub.
1281 re.sub.
1282
1282
1283 """
1283 """
1284 radius = int(regexp_match.group(1))
1284 radius = int(regexp_match.group(1))
1285
1285
1286 # Compute the first and last page number within the radius
1286 # Compute the first and last page number within the radius
1287 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1287 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1288 # -> leftmost_page = 5
1288 # -> leftmost_page = 5
1289 # -> rightmost_page = 9
1289 # -> rightmost_page = 9
1290 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1290 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1291 self.last_page,
1291 self.last_page,
1292 (radius * 2) + 1)
1292 (radius * 2) + 1)
1293 nav_items = []
1293 nav_items = []
1294
1294
1295 # Create a link to the first page (unless we are on the first page
1295 # Create a link to the first page (unless we are on the first page
1296 # or there would be no need to insert '..' spacers)
1296 # or there would be no need to insert '..' spacers)
1297 if self.page != self.first_page and self.first_page < leftmost_page:
1297 if self.page != self.first_page and self.first_page < leftmost_page:
1298 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1298 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1299
1299
1300 # Insert dots if there are pages between the first page
1300 # Insert dots if there are pages between the first page
1301 # and the currently displayed page range
1301 # and the currently displayed page range
1302 if leftmost_page - self.first_page > 1:
1302 if leftmost_page - self.first_page > 1:
1303 # Wrap in a SPAN tag if nolink_attr is set
1303 # Wrap in a SPAN tag if nolink_attr is set
1304 text = '..'
1304 text = '..'
1305 if self.dotdot_attr:
1305 if self.dotdot_attr:
1306 text = HTML.span(c=text, **self.dotdot_attr)
1306 text = HTML.span(c=text, **self.dotdot_attr)
1307 nav_items.append(text)
1307 nav_items.append(text)
1308
1308
1309 for thispage in xrange(leftmost_page, rightmost_page + 1):
1309 for thispage in xrange(leftmost_page, rightmost_page + 1):
1310 # Hilight the current page number and do not use a link
1310 # Hilight the current page number and do not use a link
1311 if thispage == self.page:
1311 if thispage == self.page:
1312 text = '%s' % (thispage,)
1312 text = '%s' % (thispage,)
1313 # Wrap in a SPAN tag if nolink_attr is set
1313 # Wrap in a SPAN tag if nolink_attr is set
1314 if self.curpage_attr:
1314 if self.curpage_attr:
1315 text = HTML.span(c=text, **self.curpage_attr)
1315 text = HTML.span(c=text, **self.curpage_attr)
1316 nav_items.append(text)
1316 nav_items.append(text)
1317 # Otherwise create just a link to that page
1317 # Otherwise create just a link to that page
1318 else:
1318 else:
1319 text = '%s' % (thispage,)
1319 text = '%s' % (thispage,)
1320 nav_items.append(self._pagerlink(thispage, text))
1320 nav_items.append(self._pagerlink(thispage, text))
1321
1321
1322 # Insert dots if there are pages between the displayed
1322 # Insert dots if there are pages between the displayed
1323 # page numbers and the end of the page range
1323 # page numbers and the end of the page range
1324 if self.last_page - rightmost_page > 1:
1324 if self.last_page - rightmost_page > 1:
1325 text = '..'
1325 text = '..'
1326 # Wrap in a SPAN tag if nolink_attr is set
1326 # Wrap in a SPAN tag if nolink_attr is set
1327 if self.dotdot_attr:
1327 if self.dotdot_attr:
1328 text = HTML.span(c=text, **self.dotdot_attr)
1328 text = HTML.span(c=text, **self.dotdot_attr)
1329 nav_items.append(text)
1329 nav_items.append(text)
1330
1330
1331 # Create a link to the very last page (unless we are on the last
1331 # Create a link to the very last page (unless we are on the last
1332 # page or there would be no need to insert '..' spacers)
1332 # page or there would be no need to insert '..' spacers)
1333 if self.page != self.last_page and rightmost_page < self.last_page:
1333 if self.page != self.last_page and rightmost_page < self.last_page:
1334 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1334 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1335
1335
1336 ## prerender links
1336 ## prerender links
1337 #_page_link = url.current()
1337 #_page_link = url.current()
1338 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1338 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1339 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1339 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1340 return self.separator.join(nav_items)
1340 return self.separator.join(nav_items)
1341
1341
1342 def pager(self, format='~2~', page_param='page', partial_param='partial',
1342 def pager(self, format='~2~', page_param='page', partial_param='partial',
1343 show_if_single_page=False, separator=' ', onclick=None,
1343 show_if_single_page=False, separator=' ', onclick=None,
1344 symbol_first='<<', symbol_last='>>',
1344 symbol_first='<<', symbol_last='>>',
1345 symbol_previous='<', symbol_next='>',
1345 symbol_previous='<', symbol_next='>',
1346 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1346 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1347 curpage_attr={'class': 'pager_curpage'},
1347 curpage_attr={'class': 'pager_curpage'},
1348 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1348 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1349
1349
1350 self.curpage_attr = curpage_attr
1350 self.curpage_attr = curpage_attr
1351 self.separator = separator
1351 self.separator = separator
1352 self.pager_kwargs = kwargs
1352 self.pager_kwargs = kwargs
1353 self.page_param = page_param
1353 self.page_param = page_param
1354 self.partial_param = partial_param
1354 self.partial_param = partial_param
1355 self.onclick = onclick
1355 self.onclick = onclick
1356 self.link_attr = link_attr
1356 self.link_attr = link_attr
1357 self.dotdot_attr = dotdot_attr
1357 self.dotdot_attr = dotdot_attr
1358
1358
1359 # Don't show navigator if there is no more than one page
1359 # Don't show navigator if there is no more than one page
1360 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1360 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1361 return ''
1361 return ''
1362
1362
1363 from string import Template
1363 from string import Template
1364 # Replace ~...~ in token format by range of pages
1364 # Replace ~...~ in token format by range of pages
1365 result = re.sub(r'~(\d+)~', self._range, format)
1365 result = re.sub(r'~(\d+)~', self._range, format)
1366
1366
1367 # Interpolate '%' variables
1367 # Interpolate '%' variables
1368 result = Template(result).safe_substitute({
1368 result = Template(result).safe_substitute({
1369 'first_page': self.first_page,
1369 'first_page': self.first_page,
1370 'last_page': self.last_page,
1370 'last_page': self.last_page,
1371 'page': self.page,
1371 'page': self.page,
1372 'page_count': self.page_count,
1372 'page_count': self.page_count,
1373 'items_per_page': self.items_per_page,
1373 'items_per_page': self.items_per_page,
1374 'first_item': self.first_item,
1374 'first_item': self.first_item,
1375 'last_item': self.last_item,
1375 'last_item': self.last_item,
1376 'item_count': self.item_count,
1376 'item_count': self.item_count,
1377 'link_first': self.page > self.first_page and \
1377 'link_first': self.page > self.first_page and \
1378 self._pagerlink(self.first_page, symbol_first) or '',
1378 self._pagerlink(self.first_page, symbol_first) or '',
1379 'link_last': self.page < self.last_page and \
1379 'link_last': self.page < self.last_page and \
1380 self._pagerlink(self.last_page, symbol_last) or '',
1380 self._pagerlink(self.last_page, symbol_last) or '',
1381 'link_previous': self.previous_page and \
1381 'link_previous': self.previous_page and \
1382 self._pagerlink(self.previous_page, symbol_previous) \
1382 self._pagerlink(self.previous_page, symbol_previous) \
1383 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1383 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1384 'link_next': self.next_page and \
1384 'link_next': self.next_page and \
1385 self._pagerlink(self.next_page, symbol_next) \
1385 self._pagerlink(self.next_page, symbol_next) \
1386 or HTML.span(symbol_next, class_="pg-next disabled")
1386 or HTML.span(symbol_next, class_="pg-next disabled")
1387 })
1387 })
1388
1388
1389 return literal(result)
1389 return literal(result)
1390
1390
1391
1391
1392 #==============================================================================
1392 #==============================================================================
1393 # REPO PAGER, PAGER FOR REPOSITORY
1393 # REPO PAGER, PAGER FOR REPOSITORY
1394 #==============================================================================
1394 #==============================================================================
1395 class RepoPage(Page):
1395 class RepoPage(Page):
1396
1396
1397 def __init__(self, collection, page=1, items_per_page=20,
1397 def __init__(self, collection, page=1, items_per_page=20,
1398 item_count=None, url=None, **kwargs):
1398 item_count=None, url=None, **kwargs):
1399
1399
1400 """Create a "RepoPage" instance. special pager for paging
1400 """Create a "RepoPage" instance. special pager for paging
1401 repository
1401 repository
1402 """
1402 """
1403 self._url_generator = url
1403 self._url_generator = url
1404
1404
1405 # Safe the kwargs class-wide so they can be used in the pager() method
1405 # Safe the kwargs class-wide so they can be used in the pager() method
1406 self.kwargs = kwargs
1406 self.kwargs = kwargs
1407
1407
1408 # Save a reference to the collection
1408 # Save a reference to the collection
1409 self.original_collection = collection
1409 self.original_collection = collection
1410
1410
1411 self.collection = collection
1411 self.collection = collection
1412
1412
1413 # The self.page is the number of the current page.
1413 # The self.page is the number of the current page.
1414 # The first page has the number 1!
1414 # The first page has the number 1!
1415 try:
1415 try:
1416 self.page = int(page) # make it int() if we get it as a string
1416 self.page = int(page) # make it int() if we get it as a string
1417 except (ValueError, TypeError):
1417 except (ValueError, TypeError):
1418 self.page = 1
1418 self.page = 1
1419
1419
1420 self.items_per_page = items_per_page
1420 self.items_per_page = items_per_page
1421
1421
1422 # Unless the user tells us how many items the collections has
1422 # Unless the user tells us how many items the collections has
1423 # we calculate that ourselves.
1423 # we calculate that ourselves.
1424 if item_count is not None:
1424 if item_count is not None:
1425 self.item_count = item_count
1425 self.item_count = item_count
1426 else:
1426 else:
1427 self.item_count = len(self.collection)
1427 self.item_count = len(self.collection)
1428
1428
1429 # Compute the number of the first and last available page
1429 # Compute the number of the first and last available page
1430 if self.item_count > 0:
1430 if self.item_count > 0:
1431 self.first_page = 1
1431 self.first_page = 1
1432 self.page_count = int(math.ceil(float(self.item_count) /
1432 self.page_count = int(math.ceil(float(self.item_count) /
1433 self.items_per_page))
1433 self.items_per_page))
1434 self.last_page = self.first_page + self.page_count - 1
1434 self.last_page = self.first_page + self.page_count - 1
1435
1435
1436 # Make sure that the requested page number is the range of
1436 # Make sure that the requested page number is the range of
1437 # valid pages
1437 # valid pages
1438 if self.page > self.last_page:
1438 if self.page > self.last_page:
1439 self.page = self.last_page
1439 self.page = self.last_page
1440 elif self.page < self.first_page:
1440 elif self.page < self.first_page:
1441 self.page = self.first_page
1441 self.page = self.first_page
1442
1442
1443 # Note: the number of items on this page can be less than
1443 # Note: the number of items on this page can be less than
1444 # items_per_page if the last page is not full
1444 # items_per_page if the last page is not full
1445 self.first_item = max(0, (self.item_count) - (self.page *
1445 self.first_item = max(0, (self.item_count) - (self.page *
1446 items_per_page))
1446 items_per_page))
1447 self.last_item = ((self.item_count - 1) - items_per_page *
1447 self.last_item = ((self.item_count - 1) - items_per_page *
1448 (self.page - 1))
1448 (self.page - 1))
1449
1449
1450 self.items = list(self.collection[self.first_item:self.last_item + 1])
1450 self.items = list(self.collection[self.first_item:self.last_item + 1])
1451
1451
1452 # Links to previous and next page
1452 # Links to previous and next page
1453 if self.page > self.first_page:
1453 if self.page > self.first_page:
1454 self.previous_page = self.page - 1
1454 self.previous_page = self.page - 1
1455 else:
1455 else:
1456 self.previous_page = None
1456 self.previous_page = None
1457
1457
1458 if self.page < self.last_page:
1458 if self.page < self.last_page:
1459 self.next_page = self.page + 1
1459 self.next_page = self.page + 1
1460 else:
1460 else:
1461 self.next_page = None
1461 self.next_page = None
1462
1462
1463 # No items available
1463 # No items available
1464 else:
1464 else:
1465 self.first_page = None
1465 self.first_page = None
1466 self.page_count = 0
1466 self.page_count = 0
1467 self.last_page = None
1467 self.last_page = None
1468 self.first_item = None
1468 self.first_item = None
1469 self.last_item = None
1469 self.last_item = None
1470 self.previous_page = None
1470 self.previous_page = None
1471 self.next_page = None
1471 self.next_page = None
1472 self.items = []
1472 self.items = []
1473
1473
1474 # This is a subclass of the 'list' type. Initialise the list now.
1474 # This is a subclass of the 'list' type. Initialise the list now.
1475 list.__init__(self, reversed(self.items))
1475 list.__init__(self, reversed(self.items))
1476
1476
1477
1477
1478 def breadcrumb_repo_link(repo):
1478 def breadcrumb_repo_link(repo):
1479 """
1479 """
1480 Makes a breadcrumbs path link to repo
1480 Makes a breadcrumbs path link to repo
1481
1481
1482 ex::
1482 ex::
1483 group >> subgroup >> repo
1483 group >> subgroup >> repo
1484
1484
1485 :param repo: a Repository instance
1485 :param repo: a Repository instance
1486 """
1486 """
1487
1487
1488 path = [
1488 path = [
1489 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name))
1489 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name))
1490 for group in repo.groups_with_parents
1490 for group in repo.groups_with_parents
1491 ] + [
1491 ] + [
1492 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name))
1492 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name))
1493 ]
1493 ]
1494
1494
1495 return literal(' &raquo; '.join(path))
1495 return literal(' &raquo; '.join(path))
1496
1496
1497
1497
1498 def breadcrumb_repo_group_link(repo_group):
1499 """
1500 Makes a breadcrumbs path link to repo
1501
1502 ex::
1503 group >> subgroup
1504
1505 :param repo_group: a Repository Group instance
1506 """
1507
1508 path = [
1509 link_to(group.name,
1510 route_path('repo_group_home', repo_group_name=group.group_name))
1511 for group in repo_group.parents
1512 ] + [
1513 link_to(repo_group.name,
1514 route_path('repo_group_home', repo_group_name=repo_group.group_name))
1515 ]
1516
1517 return literal(' &raquo; '.join(path))
1518
1519
1498 def format_byte_size_binary(file_size):
1520 def format_byte_size_binary(file_size):
1499 """
1521 """
1500 Formats file/folder sizes to standard.
1522 Formats file/folder sizes to standard.
1501 """
1523 """
1502 if file_size is None:
1524 if file_size is None:
1503 file_size = 0
1525 file_size = 0
1504
1526
1505 formatted_size = format_byte_size(file_size, binary=True)
1527 formatted_size = format_byte_size(file_size, binary=True)
1506 return formatted_size
1528 return formatted_size
1507
1529
1508
1530
1509 def urlify_text(text_, safe=True):
1531 def urlify_text(text_, safe=True):
1510 """
1532 """
1511 Extrac urls from text and make html links out of them
1533 Extrac urls from text and make html links out of them
1512
1534
1513 :param text_:
1535 :param text_:
1514 """
1536 """
1515
1537
1516 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1538 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1517 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1539 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1518
1540
1519 def url_func(match_obj):
1541 def url_func(match_obj):
1520 url_full = match_obj.groups()[0]
1542 url_full = match_obj.groups()[0]
1521 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1543 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1522 _newtext = url_pat.sub(url_func, text_)
1544 _newtext = url_pat.sub(url_func, text_)
1523 if safe:
1545 if safe:
1524 return literal(_newtext)
1546 return literal(_newtext)
1525 return _newtext
1547 return _newtext
1526
1548
1527
1549
1528 def urlify_commits(text_, repository):
1550 def urlify_commits(text_, repository):
1529 """
1551 """
1530 Extract commit ids from text and make link from them
1552 Extract commit ids from text and make link from them
1531
1553
1532 :param text_:
1554 :param text_:
1533 :param repository: repo name to build the URL with
1555 :param repository: repo name to build the URL with
1534 """
1556 """
1535
1557
1536 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1558 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1537
1559
1538 def url_func(match_obj):
1560 def url_func(match_obj):
1539 commit_id = match_obj.groups()[1]
1561 commit_id = match_obj.groups()[1]
1540 pref = match_obj.groups()[0]
1562 pref = match_obj.groups()[0]
1541 suf = match_obj.groups()[2]
1563 suf = match_obj.groups()[2]
1542
1564
1543 tmpl = (
1565 tmpl = (
1544 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1566 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1545 '%(commit_id)s</a>%(suf)s'
1567 '%(commit_id)s</a>%(suf)s'
1546 )
1568 )
1547 return tmpl % {
1569 return tmpl % {
1548 'pref': pref,
1570 'pref': pref,
1549 'cls': 'revision-link',
1571 'cls': 'revision-link',
1550 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1572 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1551 'commit_id': commit_id,
1573 'commit_id': commit_id,
1552 'suf': suf
1574 'suf': suf
1553 }
1575 }
1554
1576
1555 newtext = URL_PAT.sub(url_func, text_)
1577 newtext = URL_PAT.sub(url_func, text_)
1556
1578
1557 return newtext
1579 return newtext
1558
1580
1559
1581
1560 def _process_url_func(match_obj, repo_name, uid, entry,
1582 def _process_url_func(match_obj, repo_name, uid, entry,
1561 return_raw_data=False, link_format='html'):
1583 return_raw_data=False, link_format='html'):
1562 pref = ''
1584 pref = ''
1563 if match_obj.group().startswith(' '):
1585 if match_obj.group().startswith(' '):
1564 pref = ' '
1586 pref = ' '
1565
1587
1566 issue_id = ''.join(match_obj.groups())
1588 issue_id = ''.join(match_obj.groups())
1567
1589
1568 if link_format == 'html':
1590 if link_format == 'html':
1569 tmpl = (
1591 tmpl = (
1570 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1592 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1571 '%(issue-prefix)s%(id-repr)s'
1593 '%(issue-prefix)s%(id-repr)s'
1572 '</a>')
1594 '</a>')
1573 elif link_format == 'rst':
1595 elif link_format == 'rst':
1574 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1596 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1575 elif link_format == 'markdown':
1597 elif link_format == 'markdown':
1576 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1598 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1577 else:
1599 else:
1578 raise ValueError('Bad link_format:{}'.format(link_format))
1600 raise ValueError('Bad link_format:{}'.format(link_format))
1579
1601
1580 (repo_name_cleaned,
1602 (repo_name_cleaned,
1581 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1603 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1582
1604
1583 # variables replacement
1605 # variables replacement
1584 named_vars = {
1606 named_vars = {
1585 'id': issue_id,
1607 'id': issue_id,
1586 'repo': repo_name,
1608 'repo': repo_name,
1587 'repo_name': repo_name_cleaned,
1609 'repo_name': repo_name_cleaned,
1588 'group_name': parent_group_name
1610 'group_name': parent_group_name
1589 }
1611 }
1590 # named regex variables
1612 # named regex variables
1591 named_vars.update(match_obj.groupdict())
1613 named_vars.update(match_obj.groupdict())
1592 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1614 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1593
1615
1594 def quote_cleaner(input_str):
1616 def quote_cleaner(input_str):
1595 """Remove quotes as it's HTML"""
1617 """Remove quotes as it's HTML"""
1596 return input_str.replace('"', '')
1618 return input_str.replace('"', '')
1597
1619
1598 data = {
1620 data = {
1599 'pref': pref,
1621 'pref': pref,
1600 'cls': quote_cleaner('issue-tracker-link'),
1622 'cls': quote_cleaner('issue-tracker-link'),
1601 'url': quote_cleaner(_url),
1623 'url': quote_cleaner(_url),
1602 'id-repr': issue_id,
1624 'id-repr': issue_id,
1603 'issue-prefix': entry['pref'],
1625 'issue-prefix': entry['pref'],
1604 'serv': entry['url'],
1626 'serv': entry['url'],
1605 }
1627 }
1606 if return_raw_data:
1628 if return_raw_data:
1607 return {
1629 return {
1608 'id': issue_id,
1630 'id': issue_id,
1609 'url': _url
1631 'url': _url
1610 }
1632 }
1611 return tmpl % data
1633 return tmpl % data
1612
1634
1613
1635
1614 def get_active_pattern_entries(repo_name):
1636 def get_active_pattern_entries(repo_name):
1615 repo = None
1637 repo = None
1616 if repo_name:
1638 if repo_name:
1617 # Retrieving repo_name to avoid invalid repo_name to explode on
1639 # Retrieving repo_name to avoid invalid repo_name to explode on
1618 # IssueTrackerSettingsModel but still passing invalid name further down
1640 # IssueTrackerSettingsModel but still passing invalid name further down
1619 repo = Repository.get_by_repo_name(repo_name, cache=True)
1641 repo = Repository.get_by_repo_name(repo_name, cache=True)
1620
1642
1621 settings_model = IssueTrackerSettingsModel(repo=repo)
1643 settings_model = IssueTrackerSettingsModel(repo=repo)
1622 active_entries = settings_model.get_settings(cache=True)
1644 active_entries = settings_model.get_settings(cache=True)
1623 return active_entries
1645 return active_entries
1624
1646
1625
1647
1626 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1648 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1627
1649
1628 allowed_formats = ['html', 'rst', 'markdown']
1650 allowed_formats = ['html', 'rst', 'markdown']
1629 if link_format not in allowed_formats:
1651 if link_format not in allowed_formats:
1630 raise ValueError('Link format can be only one of:{} got {}'.format(
1652 raise ValueError('Link format can be only one of:{} got {}'.format(
1631 allowed_formats, link_format))
1653 allowed_formats, link_format))
1632
1654
1633 active_entries = active_entries or get_active_pattern_entries(repo_name)
1655 active_entries = active_entries or get_active_pattern_entries(repo_name)
1634 issues_data = []
1656 issues_data = []
1635 newtext = text_string
1657 newtext = text_string
1636
1658
1637 for uid, entry in active_entries.items():
1659 for uid, entry in active_entries.items():
1638 log.debug('found issue tracker entry with uid %s', uid)
1660 log.debug('found issue tracker entry with uid %s', uid)
1639
1661
1640 if not (entry['pat'] and entry['url']):
1662 if not (entry['pat'] and entry['url']):
1641 log.debug('skipping due to missing data')
1663 log.debug('skipping due to missing data')
1642 continue
1664 continue
1643
1665
1644 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1666 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1645 uid, entry['pat'], entry['url'], entry['pref'])
1667 uid, entry['pat'], entry['url'], entry['pref'])
1646
1668
1647 try:
1669 try:
1648 pattern = re.compile(r'%s' % entry['pat'])
1670 pattern = re.compile(r'%s' % entry['pat'])
1649 except re.error:
1671 except re.error:
1650 log.exception(
1672 log.exception(
1651 'issue tracker pattern: `%s` failed to compile',
1673 'issue tracker pattern: `%s` failed to compile',
1652 entry['pat'])
1674 entry['pat'])
1653 continue
1675 continue
1654
1676
1655 data_func = partial(
1677 data_func = partial(
1656 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1678 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1657 return_raw_data=True)
1679 return_raw_data=True)
1658
1680
1659 for match_obj in pattern.finditer(text_string):
1681 for match_obj in pattern.finditer(text_string):
1660 issues_data.append(data_func(match_obj))
1682 issues_data.append(data_func(match_obj))
1661
1683
1662 url_func = partial(
1684 url_func = partial(
1663 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1685 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1664 link_format=link_format)
1686 link_format=link_format)
1665
1687
1666 newtext = pattern.sub(url_func, newtext)
1688 newtext = pattern.sub(url_func, newtext)
1667 log.debug('processed prefix:uid `%s`', uid)
1689 log.debug('processed prefix:uid `%s`', uid)
1668
1690
1669 return newtext, issues_data
1691 return newtext, issues_data
1670
1692
1671
1693
1672 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1694 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1673 """
1695 """
1674 Parses given text message and makes proper links.
1696 Parses given text message and makes proper links.
1675 issues are linked to given issue-server, and rest is a commit link
1697 issues are linked to given issue-server, and rest is a commit link
1676
1698
1677 :param commit_text:
1699 :param commit_text:
1678 :param repository:
1700 :param repository:
1679 """
1701 """
1680 def escaper(string):
1702 def escaper(string):
1681 return string.replace('<', '&lt;').replace('>', '&gt;')
1703 return string.replace('<', '&lt;').replace('>', '&gt;')
1682
1704
1683 newtext = escaper(commit_text)
1705 newtext = escaper(commit_text)
1684
1706
1685 # extract http/https links and make them real urls
1707 # extract http/https links and make them real urls
1686 newtext = urlify_text(newtext, safe=False)
1708 newtext = urlify_text(newtext, safe=False)
1687
1709
1688 # urlify commits - extract commit ids and make link out of them, if we have
1710 # urlify commits - extract commit ids and make link out of them, if we have
1689 # the scope of repository present.
1711 # the scope of repository present.
1690 if repository:
1712 if repository:
1691 newtext = urlify_commits(newtext, repository)
1713 newtext = urlify_commits(newtext, repository)
1692
1714
1693 # process issue tracker patterns
1715 # process issue tracker patterns
1694 newtext, issues = process_patterns(newtext, repository or '',
1716 newtext, issues = process_patterns(newtext, repository or '',
1695 active_entries=active_pattern_entries)
1717 active_entries=active_pattern_entries)
1696
1718
1697 return literal(newtext)
1719 return literal(newtext)
1698
1720
1699
1721
1700 def render_binary(repo_name, file_obj):
1722 def render_binary(repo_name, file_obj):
1701 """
1723 """
1702 Choose how to render a binary file
1724 Choose how to render a binary file
1703 """
1725 """
1704
1726
1705 filename = file_obj.name
1727 filename = file_obj.name
1706
1728
1707 # images
1729 # images
1708 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1730 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1709 if fnmatch.fnmatch(filename, pat=ext):
1731 if fnmatch.fnmatch(filename, pat=ext):
1710 alt = escape(filename)
1732 alt = escape(filename)
1711 src = route_path(
1733 src = route_path(
1712 'repo_file_raw', repo_name=repo_name,
1734 'repo_file_raw', repo_name=repo_name,
1713 commit_id=file_obj.commit.raw_id,
1735 commit_id=file_obj.commit.raw_id,
1714 f_path=file_obj.path)
1736 f_path=file_obj.path)
1715 return literal(
1737 return literal(
1716 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1738 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1717
1739
1718
1740
1719 def renderer_from_filename(filename, exclude=None):
1741 def renderer_from_filename(filename, exclude=None):
1720 """
1742 """
1721 choose a renderer based on filename, this works only for text based files
1743 choose a renderer based on filename, this works only for text based files
1722 """
1744 """
1723
1745
1724 # ipython
1746 # ipython
1725 for ext in ['*.ipynb']:
1747 for ext in ['*.ipynb']:
1726 if fnmatch.fnmatch(filename, pat=ext):
1748 if fnmatch.fnmatch(filename, pat=ext):
1727 return 'jupyter'
1749 return 'jupyter'
1728
1750
1729 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1751 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1730 if is_markup:
1752 if is_markup:
1731 return is_markup
1753 return is_markup
1732 return None
1754 return None
1733
1755
1734
1756
1735 def render(source, renderer='rst', mentions=False, relative_urls=None,
1757 def render(source, renderer='rst', mentions=False, relative_urls=None,
1736 repo_name=None):
1758 repo_name=None):
1737
1759
1738 def maybe_convert_relative_links(html_source):
1760 def maybe_convert_relative_links(html_source):
1739 if relative_urls:
1761 if relative_urls:
1740 return relative_links(html_source, relative_urls)
1762 return relative_links(html_source, relative_urls)
1741 return html_source
1763 return html_source
1742
1764
1743 if renderer == 'plain':
1765 if renderer == 'plain':
1744 return literal(
1766 return literal(
1745 MarkupRenderer.plain(source, leading_newline=False))
1767 MarkupRenderer.plain(source, leading_newline=False))
1746
1768
1747 elif renderer == 'rst':
1769 elif renderer == 'rst':
1748 if repo_name:
1770 if repo_name:
1749 # process patterns on comments if we pass in repo name
1771 # process patterns on comments if we pass in repo name
1750 source, issues = process_patterns(
1772 source, issues = process_patterns(
1751 source, repo_name, link_format='rst')
1773 source, repo_name, link_format='rst')
1752
1774
1753 return literal(
1775 return literal(
1754 '<div class="rst-block">%s</div>' %
1776 '<div class="rst-block">%s</div>' %
1755 maybe_convert_relative_links(
1777 maybe_convert_relative_links(
1756 MarkupRenderer.rst(source, mentions=mentions)))
1778 MarkupRenderer.rst(source, mentions=mentions)))
1757
1779
1758 elif renderer == 'markdown':
1780 elif renderer == 'markdown':
1759 if repo_name:
1781 if repo_name:
1760 # process patterns on comments if we pass in repo name
1782 # process patterns on comments if we pass in repo name
1761 source, issues = process_patterns(
1783 source, issues = process_patterns(
1762 source, repo_name, link_format='markdown')
1784 source, repo_name, link_format='markdown')
1763
1785
1764 return literal(
1786 return literal(
1765 '<div class="markdown-block">%s</div>' %
1787 '<div class="markdown-block">%s</div>' %
1766 maybe_convert_relative_links(
1788 maybe_convert_relative_links(
1767 MarkupRenderer.markdown(source, flavored=True,
1789 MarkupRenderer.markdown(source, flavored=True,
1768 mentions=mentions)))
1790 mentions=mentions)))
1769
1791
1770 elif renderer == 'jupyter':
1792 elif renderer == 'jupyter':
1771 return literal(
1793 return literal(
1772 '<div class="ipynb">%s</div>' %
1794 '<div class="ipynb">%s</div>' %
1773 maybe_convert_relative_links(
1795 maybe_convert_relative_links(
1774 MarkupRenderer.jupyter(source)))
1796 MarkupRenderer.jupyter(source)))
1775
1797
1776 # None means just show the file-source
1798 # None means just show the file-source
1777 return None
1799 return None
1778
1800
1779
1801
1780 def commit_status(repo, commit_id):
1802 def commit_status(repo, commit_id):
1781 return ChangesetStatusModel().get_status(repo, commit_id)
1803 return ChangesetStatusModel().get_status(repo, commit_id)
1782
1804
1783
1805
1784 def commit_status_lbl(commit_status):
1806 def commit_status_lbl(commit_status):
1785 return dict(ChangesetStatus.STATUSES).get(commit_status)
1807 return dict(ChangesetStatus.STATUSES).get(commit_status)
1786
1808
1787
1809
1788 def commit_time(repo_name, commit_id):
1810 def commit_time(repo_name, commit_id):
1789 repo = Repository.get_by_repo_name(repo_name)
1811 repo = Repository.get_by_repo_name(repo_name)
1790 commit = repo.get_commit(commit_id=commit_id)
1812 commit = repo.get_commit(commit_id=commit_id)
1791 return commit.date
1813 return commit.date
1792
1814
1793
1815
1794 def get_permission_name(key):
1816 def get_permission_name(key):
1795 return dict(Permission.PERMS).get(key)
1817 return dict(Permission.PERMS).get(key)
1796
1818
1797
1819
1798 def journal_filter_help(request):
1820 def journal_filter_help(request):
1799 _ = request.translate
1821 _ = request.translate
1800 from rhodecode.lib.audit_logger import ACTIONS
1822 from rhodecode.lib.audit_logger import ACTIONS
1801 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1823 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1802
1824
1803 return _(
1825 return _(
1804 'Example filter terms:\n' +
1826 'Example filter terms:\n' +
1805 ' repository:vcs\n' +
1827 ' repository:vcs\n' +
1806 ' username:marcin\n' +
1828 ' username:marcin\n' +
1807 ' username:(NOT marcin)\n' +
1829 ' username:(NOT marcin)\n' +
1808 ' action:*push*\n' +
1830 ' action:*push*\n' +
1809 ' ip:127.0.0.1\n' +
1831 ' ip:127.0.0.1\n' +
1810 ' date:20120101\n' +
1832 ' date:20120101\n' +
1811 ' date:[20120101100000 TO 20120102]\n' +
1833 ' date:[20120101100000 TO 20120102]\n' +
1812 '\n' +
1834 '\n' +
1813 'Actions: {actions}\n' +
1835 'Actions: {actions}\n' +
1814 '\n' +
1836 '\n' +
1815 'Generate wildcards using \'*\' character:\n' +
1837 'Generate wildcards using \'*\' character:\n' +
1816 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1838 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1817 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1839 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1818 '\n' +
1840 '\n' +
1819 'Optional AND / OR operators in queries\n' +
1841 'Optional AND / OR operators in queries\n' +
1820 ' "repository:vcs OR repository:test"\n' +
1842 ' "repository:vcs OR repository:test"\n' +
1821 ' "username:test AND repository:test*"\n'
1843 ' "username:test AND repository:test*"\n'
1822 ).format(actions=actions)
1844 ).format(actions=actions)
1823
1845
1824
1846
1825 def not_mapped_error(repo_name):
1847 def not_mapped_error(repo_name):
1826 from rhodecode.translation import _
1848 from rhodecode.translation import _
1827 flash(_('%s repository is not mapped to db perhaps'
1849 flash(_('%s repository is not mapped to db perhaps'
1828 ' it was created or renamed from the filesystem'
1850 ' it was created or renamed from the filesystem'
1829 ' please run the application again'
1851 ' please run the application again'
1830 ' in order to rescan repositories') % repo_name, category='error')
1852 ' in order to rescan repositories') % repo_name, category='error')
1831
1853
1832
1854
1833 def ip_range(ip_addr):
1855 def ip_range(ip_addr):
1834 from rhodecode.model.db import UserIpMap
1856 from rhodecode.model.db import UserIpMap
1835 s, e = UserIpMap._get_ip_range(ip_addr)
1857 s, e = UserIpMap._get_ip_range(ip_addr)
1836 return '%s - %s' % (s, e)
1858 return '%s - %s' % (s, e)
1837
1859
1838
1860
1839 def form(url, method='post', needs_csrf_token=True, **attrs):
1861 def form(url, method='post', needs_csrf_token=True, **attrs):
1840 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1862 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1841 if method.lower() != 'get' and needs_csrf_token:
1863 if method.lower() != 'get' and needs_csrf_token:
1842 raise Exception(
1864 raise Exception(
1843 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1865 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1844 'CSRF token. If the endpoint does not require such token you can ' +
1866 'CSRF token. If the endpoint does not require such token you can ' +
1845 'explicitly set the parameter needs_csrf_token to false.')
1867 'explicitly set the parameter needs_csrf_token to false.')
1846
1868
1847 return wh_form(url, method=method, **attrs)
1869 return wh_form(url, method=method, **attrs)
1848
1870
1849
1871
1850 def secure_form(form_url, method="POST", multipart=False, **attrs):
1872 def secure_form(form_url, method="POST", multipart=False, **attrs):
1851 """Start a form tag that points the action to an url. This
1873 """Start a form tag that points the action to an url. This
1852 form tag will also include the hidden field containing
1874 form tag will also include the hidden field containing
1853 the auth token.
1875 the auth token.
1854
1876
1855 The url options should be given either as a string, or as a
1877 The url options should be given either as a string, or as a
1856 ``url()`` function. The method for the form defaults to POST.
1878 ``url()`` function. The method for the form defaults to POST.
1857
1879
1858 Options:
1880 Options:
1859
1881
1860 ``multipart``
1882 ``multipart``
1861 If set to True, the enctype is set to "multipart/form-data".
1883 If set to True, the enctype is set to "multipart/form-data".
1862 ``method``
1884 ``method``
1863 The method to use when submitting the form, usually either
1885 The method to use when submitting the form, usually either
1864 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1886 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1865 hidden input with name _method is added to simulate the verb
1887 hidden input with name _method is added to simulate the verb
1866 over POST.
1888 over POST.
1867
1889
1868 """
1890 """
1869 from webhelpers.pylonslib.secure_form import insecure_form
1891 from webhelpers.pylonslib.secure_form import insecure_form
1870
1892
1871 if 'request' in attrs:
1893 if 'request' in attrs:
1872 session = attrs['request'].session
1894 session = attrs['request'].session
1873 del attrs['request']
1895 del attrs['request']
1874 else:
1896 else:
1875 raise ValueError(
1897 raise ValueError(
1876 'Calling this form requires request= to be passed as argument')
1898 'Calling this form requires request= to be passed as argument')
1877
1899
1878 form = insecure_form(form_url, method, multipart, **attrs)
1900 form = insecure_form(form_url, method, multipart, **attrs)
1879 token = literal(
1901 token = literal(
1880 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1902 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1881 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1903 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1882
1904
1883 return literal("%s\n%s" % (form, token))
1905 return literal("%s\n%s" % (form, token))
1884
1906
1885
1907
1886 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1908 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1887 select_html = select(name, selected, options, **attrs)
1909 select_html = select(name, selected, options, **attrs)
1888 select2 = """
1910 select2 = """
1889 <script>
1911 <script>
1890 $(document).ready(function() {
1912 $(document).ready(function() {
1891 $('#%s').select2({
1913 $('#%s').select2({
1892 containerCssClass: 'drop-menu',
1914 containerCssClass: 'drop-menu',
1893 dropdownCssClass: 'drop-menu-dropdown',
1915 dropdownCssClass: 'drop-menu-dropdown',
1894 dropdownAutoWidth: true%s
1916 dropdownAutoWidth: true%s
1895 });
1917 });
1896 });
1918 });
1897 </script>
1919 </script>
1898 """
1920 """
1899 filter_option = """,
1921 filter_option = """,
1900 minimumResultsForSearch: -1
1922 minimumResultsForSearch: -1
1901 """
1923 """
1902 input_id = attrs.get('id') or name
1924 input_id = attrs.get('id') or name
1903 filter_enabled = "" if enable_filter else filter_option
1925 filter_enabled = "" if enable_filter else filter_option
1904 select_script = literal(select2 % (input_id, filter_enabled))
1926 select_script = literal(select2 % (input_id, filter_enabled))
1905
1927
1906 return literal(select_html+select_script)
1928 return literal(select_html+select_script)
1907
1929
1908
1930
1909 def get_visual_attr(tmpl_context_var, attr_name):
1931 def get_visual_attr(tmpl_context_var, attr_name):
1910 """
1932 """
1911 A safe way to get a variable from visual variable of template context
1933 A safe way to get a variable from visual variable of template context
1912
1934
1913 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1935 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1914 :param attr_name: name of the attribute we fetch from the c.visual
1936 :param attr_name: name of the attribute we fetch from the c.visual
1915 """
1937 """
1916 visual = getattr(tmpl_context_var, 'visual', None)
1938 visual = getattr(tmpl_context_var, 'visual', None)
1917 if not visual:
1939 if not visual:
1918 return
1940 return
1919 else:
1941 else:
1920 return getattr(visual, attr_name, None)
1942 return getattr(visual, attr_name, None)
1921
1943
1922
1944
1923 def get_last_path_part(file_node):
1945 def get_last_path_part(file_node):
1924 if not file_node.path:
1946 if not file_node.path:
1925 return u''
1947 return u''
1926
1948
1927 path = safe_unicode(file_node.path.split('/')[-1])
1949 path = safe_unicode(file_node.path.split('/')[-1])
1928 return u'../' + path
1950 return u'../' + path
1929
1951
1930
1952
1931 def route_url(*args, **kwargs):
1953 def route_url(*args, **kwargs):
1932 """
1954 """
1933 Wrapper around pyramids `route_url` (fully qualified url) function.
1955 Wrapper around pyramids `route_url` (fully qualified url) function.
1934 """
1956 """
1935 req = get_current_request()
1957 req = get_current_request()
1936 return req.route_url(*args, **kwargs)
1958 return req.route_url(*args, **kwargs)
1937
1959
1938
1960
1939 def route_path(*args, **kwargs):
1961 def route_path(*args, **kwargs):
1940 """
1962 """
1941 Wrapper around pyramids `route_path` function.
1963 Wrapper around pyramids `route_path` function.
1942 """
1964 """
1943 req = get_current_request()
1965 req = get_current_request()
1944 return req.route_path(*args, **kwargs)
1966 return req.route_path(*args, **kwargs)
1945
1967
1946
1968
1947 def route_path_or_none(*args, **kwargs):
1969 def route_path_or_none(*args, **kwargs):
1948 try:
1970 try:
1949 return route_path(*args, **kwargs)
1971 return route_path(*args, **kwargs)
1950 except KeyError:
1972 except KeyError:
1951 return None
1973 return None
1952
1974
1953
1975
1954 def current_route_path(request, **kw):
1976 def current_route_path(request, **kw):
1955 new_args = request.GET.mixed()
1977 new_args = request.GET.mixed()
1956 new_args.update(kw)
1978 new_args.update(kw)
1957 return request.current_route_path(_query=new_args)
1979 return request.current_route_path(_query=new_args)
1958
1980
1959
1981
1960 def api_call_example(method, args):
1982 def api_call_example(method, args):
1961 """
1983 """
1962 Generates an API call example via CURL
1984 Generates an API call example via CURL
1963 """
1985 """
1964 args_json = json.dumps(OrderedDict([
1986 args_json = json.dumps(OrderedDict([
1965 ('id', 1),
1987 ('id', 1),
1966 ('auth_token', 'SECRET'),
1988 ('auth_token', 'SECRET'),
1967 ('method', method),
1989 ('method', method),
1968 ('args', args)
1990 ('args', args)
1969 ]))
1991 ]))
1970 return literal(
1992 return literal(
1971 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
1993 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
1972 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1994 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
1973 "and needs to be of `api calls` role."
1995 "and needs to be of `api calls` role."
1974 .format(
1996 .format(
1975 api_url=route_url('apiv2'),
1997 api_url=route_url('apiv2'),
1976 token_url=route_url('my_account_auth_tokens'),
1998 token_url=route_url('my_account_auth_tokens'),
1977 data=args_json))
1999 data=args_json))
1978
2000
1979
2001
1980 def notification_description(notification, request):
2002 def notification_description(notification, request):
1981 """
2003 """
1982 Generate notification human readable description based on notification type
2004 Generate notification human readable description based on notification type
1983 """
2005 """
1984 from rhodecode.model.notification import NotificationModel
2006 from rhodecode.model.notification import NotificationModel
1985 return NotificationModel().make_description(
2007 return NotificationModel().make_description(
1986 notification, translate=request.translate)
2008 notification, translate=request.translate)
1987
2009
1988
2010
1989 def go_import_header(request, db_repo=None):
2011 def go_import_header(request, db_repo=None):
1990 """
2012 """
1991 Creates a header for go-import functionality in Go Lang
2013 Creates a header for go-import functionality in Go Lang
1992 """
2014 """
1993
2015
1994 if not db_repo:
2016 if not db_repo:
1995 return
2017 return
1996 if 'go-get' not in request.GET:
2018 if 'go-get' not in request.GET:
1997 return
2019 return
1998
2020
1999 clone_url = db_repo.clone_url()
2021 clone_url = db_repo.clone_url()
2000 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2022 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2001 # we have a repo and go-get flag,
2023 # we have a repo and go-get flag,
2002 return literal('<meta name="go-import" content="{} {} {}">'.format(
2024 return literal('<meta name="go-import" content="{} {} {}">'.format(
2003 prefix, db_repo.repo_type, clone_url))
2025 prefix, db_repo.repo_type, clone_url))
2004
2026
2005
2027
2006 def reviewer_as_json(*args, **kwargs):
2028 def reviewer_as_json(*args, **kwargs):
2007 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2029 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2008 return _reviewer_as_json(*args, **kwargs)
2030 return _reviewer_as_json(*args, **kwargs)
2009
2031
2010
2032
2011 def get_repo_view_type(request):
2033 def get_repo_view_type(request):
2012 route_name = request.matched_route.name
2034 route_name = request.matched_route.name
2013 route_to_view_type = {
2035 route_to_view_type = {
2014 'repo_changelog': 'changelog',
2036 'repo_changelog': 'changelog',
2015 'repo_files': 'files',
2037 'repo_files': 'files',
2016 'repo_summary': 'summary',
2038 'repo_summary': 'summary',
2017 'repo_commit': 'commit'
2039 'repo_commit': 'commit'
2018
2040
2019 }
2041 }
2020 return route_to_view_type.get(route_name)
2042 return route_to_view_type.get(route_name)
@@ -1,99 +1,103 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2019 RhodeCode GmbH
3 # Copyright (C) 2012-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 Index schema for RhodeCode
22 Index schema for RhodeCode
23 """
23 """
24
24
25 import importlib
25 import importlib
26 import logging
26 import logging
27
27
28 from rhodecode.lib.index.search_utils import normalize_text_for_matching
28 from rhodecode.lib.index.search_utils import normalize_text_for_matching
29
29
30 log = logging.getLogger(__name__)
30 log = logging.getLogger(__name__)
31
31
32 # leave defaults for backward compat
32 # leave defaults for backward compat
33 default_searcher = 'rhodecode.lib.index.whoosh'
33 default_searcher = 'rhodecode.lib.index.whoosh'
34 default_location = '%(here)s/data/index'
34 default_location = '%(here)s/data/index'
35
35
36 ES_VERSION_2 = '2'
36 ES_VERSION_2 = '2'
37 ES_VERSION_6 = '6'
37 ES_VERSION_6 = '6'
38 # for legacy reasons we keep 2 compat as default
38 # for legacy reasons we keep 2 compat as default
39 DEFAULT_ES_VERSION = ES_VERSION_2
39 DEFAULT_ES_VERSION = ES_VERSION_2
40
40
41 from rhodecode_tools.lib.fts_index.elasticsearch_engine_6 import \
41 from rhodecode_tools.lib.fts_index.elasticsearch_engine_6 import \
42 ES_CONFIG # pragma: no cover
42 ES_CONFIG # pragma: no cover
43
43
44
44
45 class BaseSearcher(object):
45 class BaseSearcher(object):
46 query_lang_doc = ''
46 query_lang_doc = ''
47 es_version = None
47 es_version = None
48 name = None
48 name = None
49
49
50 def __init__(self):
50 def __init__(self):
51 pass
51 pass
52
52
53 def cleanup(self):
53 def cleanup(self):
54 pass
54 pass
55
55
56 def search(self, query, document_type, search_user,
56 def search(self, query, document_type, search_user,
57 repo_name=None, repo_group_name=None,
57 repo_name=None, repo_group_name=None,
58 raise_on_exc=True):
58 raise_on_exc=True):
59 raise Exception('NotImplemented')
59 raise Exception('NotImplemented')
60
60
61 @staticmethod
61 @staticmethod
62 def query_to_mark(query, default_field=None):
62 def query_to_mark(query, default_field=None):
63 """
63 """
64 Formats the query to mark token for jquery.mark.js highlighting. ES could
64 Formats the query to mark token for jquery.mark.js highlighting. ES could
65 have a different format optionally.
65 have a different format optionally.
66
66
67 :param default_field:
67 :param default_field:
68 :param query:
68 :param query:
69 """
69 """
70 return ' '.join(normalize_text_for_matching(query).split())
70 return ' '.join(normalize_text_for_matching(query).split())
71
71
72 @property
72 @property
73 def is_es_6(self):
73 def is_es_6(self):
74 return self.es_version == ES_VERSION_6
74 return self.es_version == ES_VERSION_6
75
75
76 def get_handlers(self):
76 def get_handlers(self):
77 return {}
77 return {}
78
78
79 @staticmethod
80 def extract_search_tags(query):
81 return []
82
79
83
80 def search_config(config, prefix='search.'):
84 def search_config(config, prefix='search.'):
81 _config = {}
85 _config = {}
82 for key in config.keys():
86 for key in config.keys():
83 if key.startswith(prefix):
87 if key.startswith(prefix):
84 _config[key[len(prefix):]] = config[key]
88 _config[key[len(prefix):]] = config[key]
85 return _config
89 return _config
86
90
87
91
88 def searcher_from_config(config, prefix='search.'):
92 def searcher_from_config(config, prefix='search.'):
89 _config = search_config(config, prefix)
93 _config = search_config(config, prefix)
90
94
91 if 'location' not in _config:
95 if 'location' not in _config:
92 _config['location'] = default_location
96 _config['location'] = default_location
93 if 'es_version' not in _config:
97 if 'es_version' not in _config:
94 # use old legacy ES version set to 2
98 # use old legacy ES version set to 2
95 _config['es_version'] = '2'
99 _config['es_version'] = '2'
96
100
97 imported = importlib.import_module(_config.get('module', default_searcher))
101 imported = importlib.import_module(_config.get('module', default_searcher))
98 searcher = imported.Searcher(config=_config)
102 searcher = imported.Searcher(config=_config)
99 return searcher
103 return searcher
@@ -1,257 +1,197 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2012-2019 RhodeCode GmbH
3 # Copyright (C) 2012-2019 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 import re
20 import re
21
21
22 import pygments.filter
22 import pygments.filter
23 import pygments.filters
23 import pygments.filters
24 from pygments.token import Comment
24 from pygments.token import Comment
25
25
26 HL_BEG_MARKER = '__RCSearchHLMarkBEG__'
26 HL_BEG_MARKER = '__RCSearchHLMarkBEG__'
27 HL_END_MARKER = '__RCSearchHLMarkEND__'
27 HL_END_MARKER = '__RCSearchHLMarkEND__'
28 HL_MARKER_RE = '{}(.*?){}'.format(HL_BEG_MARKER, HL_END_MARKER)
28 HL_MARKER_RE = '{}(.*?){}'.format(HL_BEG_MARKER, HL_END_MARKER)
29
29
30
30
31 class ElasticSearchHLFilter(pygments.filters.Filter):
31 class ElasticSearchHLFilter(pygments.filters.Filter):
32 _names = [HL_BEG_MARKER, HL_END_MARKER]
32 _names = [HL_BEG_MARKER, HL_END_MARKER]
33
33
34 def __init__(self, **options):
34 def __init__(self, **options):
35 pygments.filters.Filter.__init__(self, **options)
35 pygments.filters.Filter.__init__(self, **options)
36
36
37 def filter(self, lexer, stream):
37 def filter(self, lexer, stream):
38 def tokenize(_value):
38 def tokenize(_value):
39 for token in re.split('({}|{})'.format(
39 for token in re.split('({}|{})'.format(
40 self._names[0], self._names[1]), _value):
40 self._names[0], self._names[1]), _value):
41 if token:
41 if token:
42 yield token
42 yield token
43
43
44 hl = False
44 hl = False
45 for ttype, value in stream:
45 for ttype, value in stream:
46
46
47 if self._names[0] in value or self._names[1] in value:
47 if self._names[0] in value or self._names[1] in value:
48 for item in tokenize(value):
48 for item in tokenize(value):
49 if item == self._names[0]:
49 if item == self._names[0]:
50 # skip marker, but start HL
50 # skip marker, but start HL
51 hl = True
51 hl = True
52 continue
52 continue
53 elif item == self._names[1]:
53 elif item == self._names[1]:
54 hl = False
54 hl = False
55 continue
55 continue
56
56
57 if hl:
57 if hl:
58 yield Comment.ElasticMatch, item
58 yield Comment.ElasticMatch, item
59 else:
59 else:
60 yield ttype, item
60 yield ttype, item
61 else:
61 else:
62 if hl:
62 if hl:
63 yield Comment.ElasticMatch, value
63 yield Comment.ElasticMatch, value
64 else:
64 else:
65 yield ttype, value
65 yield ttype, value
66
66
67
67
68 def extract_phrases(text_query):
68 def extract_phrases(text_query):
69 """
69 """
70 Extracts phrases from search term string making sure phrases
70 Extracts phrases from search term string making sure phrases
71 contained in double quotes are kept together - and discarding empty values
71 contained in double quotes are kept together - and discarding empty values
72 or fully whitespace values eg.
72 or fully whitespace values eg.
73
73
74 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more']
74 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more']
75
75
76 """
76 """
77
77
78 in_phrase = False
78 in_phrase = False
79 buf = ''
79 buf = ''
80 phrases = []
80 phrases = []
81 for char in text_query:
81 for char in text_query:
82 if in_phrase:
82 if in_phrase:
83 if char == '"': # end phrase
83 if char == '"': # end phrase
84 phrases.append(buf)
84 phrases.append(buf)
85 buf = ''
85 buf = ''
86 in_phrase = False
86 in_phrase = False
87 continue
87 continue
88 else:
88 else:
89 buf += char
89 buf += char
90 continue
90 continue
91 else:
91 else:
92 if char == '"': # start phrase
92 if char == '"': # start phrase
93 in_phrase = True
93 in_phrase = True
94 phrases.append(buf)
94 phrases.append(buf)
95 buf = ''
95 buf = ''
96 continue
96 continue
97 elif char == ' ':
97 elif char == ' ':
98 phrases.append(buf)
98 phrases.append(buf)
99 buf = ''
99 buf = ''
100 continue
100 continue
101 else:
101 else:
102 buf += char
102 buf += char
103
103
104 phrases.append(buf)
104 phrases.append(buf)
105 phrases = [phrase.strip() for phrase in phrases if phrase.strip()]
105 phrases = [phrase.strip() for phrase in phrases if phrase.strip()]
106 return phrases
106 return phrases
107
107
108
108
109 def get_matching_phrase_offsets(text, phrases):
109 def get_matching_phrase_offsets(text, phrases):
110 """
110 """
111 Returns a list of string offsets in `text` that the list of `terms` match
111 Returns a list of string offsets in `text` that the list of `terms` match
112
112
113 >>> get_matching_phrase_offsets('some text here', ['some', 'here'])
113 >>> get_matching_phrase_offsets('some text here', ['some', 'here'])
114 [(0, 4), (10, 14)]
114 [(0, 4), (10, 14)]
115
115
116 """
116 """
117 phrases = phrases or []
117 phrases = phrases or []
118 offsets = []
118 offsets = []
119
119
120 for phrase in phrases:
120 for phrase in phrases:
121 for match in re.finditer(phrase, text):
121 for match in re.finditer(phrase, text):
122 offsets.append((match.start(), match.end()))
122 offsets.append((match.start(), match.end()))
123
123
124 return offsets
124 return offsets
125
125
126
126
127 def get_matching_markers_offsets(text, markers=None):
127 def get_matching_markers_offsets(text, markers=None):
128 """
128 """
129 Returns a list of string offsets in `text` that the are between matching markers
129 Returns a list of string offsets in `text` that the are between matching markers
130
130
131 >>> get_matching_markers_offsets('$1some$2 text $1here$2 marked', ['\$1(.*?)\$2'])
131 >>> get_matching_markers_offsets('$1some$2 text $1here$2 marked', ['\$1(.*?)\$2'])
132 [(0, 5), (16, 22)]
132 [(0, 5), (16, 22)]
133
133
134 """
134 """
135 markers = markers or [HL_MARKER_RE]
135 markers = markers or [HL_MARKER_RE]
136 offsets = []
136 offsets = []
137
137
138 if markers:
138 if markers:
139 for mark in markers:
139 for mark in markers:
140 for match in re.finditer(mark, text):
140 for match in re.finditer(mark, text):
141 offsets.append((match.start(), match.end()))
141 offsets.append((match.start(), match.end()))
142
142
143 return offsets
143 return offsets
144
144
145
145
146 def normalize_text_for_matching(x):
146 def normalize_text_for_matching(x):
147 """
147 """
148 Replaces all non alfanum characters to spaces and lower cases the string,
148 Replaces all non alfanum characters to spaces and lower cases the string,
149 useful for comparing two text strings without punctuation
149 useful for comparing two text strings without punctuation
150 """
150 """
151 return re.sub(r'[^\w]', ' ', x.lower())
151 return re.sub(r'[^\w]', ' ', x.lower())
152
152
153
153
154 def get_matching_line_offsets(lines, terms=None, markers=None):
154 def get_matching_line_offsets(lines, terms=None, markers=None):
155 """ Return a set of `lines` indices (starting from 1) matching a
155 """ Return a set of `lines` indices (starting from 1) matching a
156 text search query, along with `context` lines above/below matching lines
156 text search query, along with `context` lines above/below matching lines
157
157
158 :param lines: list of strings representing lines
158 :param lines: list of strings representing lines
159 :param terms: search term string to match in lines eg. 'some text'
159 :param terms: search term string to match in lines eg. 'some text'
160 :param markers: instead of terms, use highlight markers instead that
160 :param markers: instead of terms, use highlight markers instead that
161 mark beginning and end for matched item. eg. ['START(.*?)END']
161 mark beginning and end for matched item. eg. ['START(.*?)END']
162
162
163 eg.
163 eg.
164
164
165 text = '''
165 text = '''
166 words words words
166 words words words
167 words words words
167 words words words
168 some text some
168 some text some
169 words words words
169 words words words
170 words words words
170 words words words
171 text here what
171 text here what
172 '''
172 '''
173 get_matching_line_offsets(text, 'text', context=1)
173 get_matching_line_offsets(text, 'text', context=1)
174 6, {3: [(5, 9)], 6: [(0, 4)]]
174 6, {3: [(5, 9)], 6: [(0, 4)]]
175
175
176 """
176 """
177 matching_lines = {}
177 matching_lines = {}
178 line_index = 0
178 line_index = 0
179
179
180 if terms:
180 if terms:
181 phrases = [normalize_text_for_matching(phrase)
181 phrases = [normalize_text_for_matching(phrase)
182 for phrase in extract_phrases(terms)]
182 for phrase in extract_phrases(terms)]
183
183
184 for line_index, line in enumerate(lines.splitlines(), start=1):
184 for line_index, line in enumerate(lines.splitlines(), start=1):
185 normalized_line = normalize_text_for_matching(line)
185 normalized_line = normalize_text_for_matching(line)
186 match_offsets = get_matching_phrase_offsets(normalized_line, phrases)
186 match_offsets = get_matching_phrase_offsets(normalized_line, phrases)
187 if match_offsets:
187 if match_offsets:
188 matching_lines[line_index] = match_offsets
188 matching_lines[line_index] = match_offsets
189
189
190 else:
190 else:
191 markers = markers or [HL_MARKER_RE]
191 markers = markers or [HL_MARKER_RE]
192 for line_index, line in enumerate(lines.splitlines(), start=1):
192 for line_index, line in enumerate(lines.splitlines(), start=1):
193 match_offsets = get_matching_markers_offsets(line, markers=markers)
193 match_offsets = get_matching_markers_offsets(line, markers=markers)
194 if match_offsets:
194 if match_offsets:
195 matching_lines[line_index] = match_offsets
195 matching_lines[line_index] = match_offsets
196
196
197 return line_index, matching_lines
197 return line_index, matching_lines
198
199
200 def lucene_query_parser():
201 # from pyparsing lucene_grammar
202 from pyparsing import (
203 Literal, CaselessKeyword, Forward, Regex, QuotedString, Suppress,
204 Optional, Group, infixNotation, opAssoc, ParserElement, pyparsing_common)
205
206 ParserElement.enablePackrat()
207
208 COLON, LBRACK, RBRACK, LBRACE, RBRACE, TILDE, CARAT = map(Literal, ":[]{}~^")
209 LPAR, RPAR = map(Suppress, "()")
210 and_, or_, not_, to_ = map(CaselessKeyword, "AND OR NOT TO".split())
211 keyword = and_ | or_ | not_ | to_
212
213 expression = Forward()
214
215 valid_word = Regex(r'([a-zA-Z0-9*_+.-]|\\[!(){}\[\]^"~*?\\:])+').setName("word")
216 valid_word.setParseAction(
217 lambda t: t[0]
218 .replace('\\\\', chr(127))
219 .replace('\\', '')
220 .replace(chr(127), '\\')
221 )
222
223 string = QuotedString('"')
224
225 required_modifier = Literal("+")("required")
226 prohibit_modifier = Literal("-")("prohibit")
227 integer = Regex(r"\d+").setParseAction(lambda t: int(t[0]))
228 proximity_modifier = Group(TILDE + integer("proximity"))
229 number = pyparsing_common.fnumber()
230 fuzzy_modifier = TILDE + Optional(number, default=0.5)("fuzzy")
231
232 term = Forward()
233 field_name = valid_word().setName("fieldname")
234 incl_range_search = Group(LBRACK + term("lower") + to_ + term("upper") + RBRACK)
235 excl_range_search = Group(LBRACE + term("lower") + to_ + term("upper") + RBRACE)
236 range_search = incl_range_search("incl_range") | excl_range_search("excl_range")
237 boost = (CARAT + number("boost"))
238
239 string_expr = Group(string + proximity_modifier) | string
240 word_expr = Group(valid_word + fuzzy_modifier) | valid_word
241 term << (Optional(field_name("field") + COLON) +
242 (word_expr | string_expr | range_search | Group(
243 LPAR + expression + RPAR)) +
244 Optional(boost))
245 term.setParseAction(lambda t: [t] if 'field' in t or 'boost' in t else None)
246
247 expression << infixNotation(
248 term,
249 [
250 (required_modifier | prohibit_modifier, 1, opAssoc.RIGHT),
251 ((not_ | '!').setParseAction(lambda: "NOT"), 1, opAssoc.RIGHT),
252 ((and_ | '&&').setParseAction(lambda: "AND"), 2, opAssoc.LEFT),
253 (Optional(or_ | '||').setParseAction(lambda: "OR"), 2, opAssoc.LEFT),
254 ]
255 )
256
257 return expression
@@ -1,2470 +1,2474 b''
1 //Primary CSS
1 //Primary CSS
2
2
3 //--- IMPORTS ------------------//
3 //--- IMPORTS ------------------//
4
4
5 @import 'helpers';
5 @import 'helpers';
6 @import 'mixins';
6 @import 'mixins';
7 @import 'rcicons';
7 @import 'rcicons';
8 @import 'variables';
8 @import 'variables';
9 @import 'bootstrap-variables';
9 @import 'bootstrap-variables';
10 @import 'form-bootstrap';
10 @import 'form-bootstrap';
11 @import 'codemirror';
11 @import 'codemirror';
12 @import 'legacy_code_styles';
12 @import 'legacy_code_styles';
13 @import 'readme-box';
13 @import 'readme-box';
14 @import 'progress-bar';
14 @import 'progress-bar';
15
15
16 @import 'type';
16 @import 'type';
17 @import 'alerts';
17 @import 'alerts';
18 @import 'buttons';
18 @import 'buttons';
19 @import 'tags';
19 @import 'tags';
20 @import 'code-block';
20 @import 'code-block';
21 @import 'examples';
21 @import 'examples';
22 @import 'login';
22 @import 'login';
23 @import 'main-content';
23 @import 'main-content';
24 @import 'select2';
24 @import 'select2';
25 @import 'comments';
25 @import 'comments';
26 @import 'panels-bootstrap';
26 @import 'panels-bootstrap';
27 @import 'panels';
27 @import 'panels';
28 @import 'deform';
28 @import 'deform';
29
29
30 //--- BASE ------------------//
30 //--- BASE ------------------//
31 .noscript-error {
31 .noscript-error {
32 top: 0;
32 top: 0;
33 left: 0;
33 left: 0;
34 width: 100%;
34 width: 100%;
35 z-index: 101;
35 z-index: 101;
36 text-align: center;
36 text-align: center;
37 font-size: 120%;
37 font-size: 120%;
38 color: white;
38 color: white;
39 background-color: @alert2;
39 background-color: @alert2;
40 padding: 5px 0 5px 0;
40 padding: 5px 0 5px 0;
41 font-weight: @text-semibold-weight;
41 font-weight: @text-semibold-weight;
42 font-family: @text-semibold;
42 font-family: @text-semibold;
43 }
43 }
44
44
45 html {
45 html {
46 display: table;
46 display: table;
47 height: 100%;
47 height: 100%;
48 width: 100%;
48 width: 100%;
49 }
49 }
50
50
51 body {
51 body {
52 display: table-cell;
52 display: table-cell;
53 width: 100%;
53 width: 100%;
54 }
54 }
55
55
56 //--- LAYOUT ------------------//
56 //--- LAYOUT ------------------//
57
57
58 .hidden{
58 .hidden{
59 display: none !important;
59 display: none !important;
60 }
60 }
61
61
62 .box{
62 .box{
63 float: left;
63 float: left;
64 width: 100%;
64 width: 100%;
65 }
65 }
66
66
67 .browser-header {
67 .browser-header {
68 clear: both;
68 clear: both;
69 }
69 }
70 .main {
70 .main {
71 clear: both;
71 clear: both;
72 padding:0 0 @pagepadding;
72 padding:0 0 @pagepadding;
73 height: auto;
73 height: auto;
74
74
75 &:after { //clearfix
75 &:after { //clearfix
76 content:"";
76 content:"";
77 clear:both;
77 clear:both;
78 width:100%;
78 width:100%;
79 display:block;
79 display:block;
80 }
80 }
81 }
81 }
82
82
83 .action-link{
83 .action-link{
84 margin-left: @padding;
84 margin-left: @padding;
85 padding-left: @padding;
85 padding-left: @padding;
86 border-left: @border-thickness solid @border-default-color;
86 border-left: @border-thickness solid @border-default-color;
87 }
87 }
88
88
89 input + .action-link, .action-link.first{
89 input + .action-link, .action-link.first{
90 border-left: none;
90 border-left: none;
91 }
91 }
92
92
93 .action-link.last{
93 .action-link.last{
94 margin-right: @padding;
94 margin-right: @padding;
95 padding-right: @padding;
95 padding-right: @padding;
96 }
96 }
97
97
98 .action-link.active,
98 .action-link.active,
99 .action-link.active a{
99 .action-link.active a{
100 color: @grey4;
100 color: @grey4;
101 }
101 }
102
102
103 .action-link.disabled {
103 .action-link.disabled {
104 color: @grey4;
104 color: @grey4;
105 cursor: inherit;
105 cursor: inherit;
106 }
106 }
107
107
108 .clipboard-action {
108 .clipboard-action {
109 cursor: pointer;
109 cursor: pointer;
110 }
110 }
111
111
112 ul.simple-list{
112 ul.simple-list{
113 list-style: none;
113 list-style: none;
114 margin: 0;
114 margin: 0;
115 padding: 0;
115 padding: 0;
116 }
116 }
117
117
118 .main-content {
118 .main-content {
119 padding-bottom: @pagepadding;
119 padding-bottom: @pagepadding;
120 }
120 }
121
121
122 .wide-mode-wrapper {
122 .wide-mode-wrapper {
123 max-width:4000px !important;
123 max-width:4000px !important;
124 }
124 }
125
125
126 .wrapper {
126 .wrapper {
127 position: relative;
127 position: relative;
128 max-width: @wrapper-maxwidth;
128 max-width: @wrapper-maxwidth;
129 margin: 0 auto;
129 margin: 0 auto;
130 }
130 }
131
131
132 #content {
132 #content {
133 clear: both;
133 clear: both;
134 padding: 0 @contentpadding;
134 padding: 0 @contentpadding;
135 }
135 }
136
136
137 .advanced-settings-fields{
137 .advanced-settings-fields{
138 input{
138 input{
139 margin-left: @textmargin;
139 margin-left: @textmargin;
140 margin-right: @padding/2;
140 margin-right: @padding/2;
141 }
141 }
142 }
142 }
143
143
144 .cs_files_title {
144 .cs_files_title {
145 margin: @pagepadding 0 0;
145 margin: @pagepadding 0 0;
146 }
146 }
147
147
148 input.inline[type="file"] {
148 input.inline[type="file"] {
149 display: inline;
149 display: inline;
150 }
150 }
151
151
152 .error_page {
152 .error_page {
153 margin: 10% auto;
153 margin: 10% auto;
154
154
155 h1 {
155 h1 {
156 color: @grey2;
156 color: @grey2;
157 }
157 }
158
158
159 .alert {
159 .alert {
160 margin: @padding 0;
160 margin: @padding 0;
161 }
161 }
162
162
163 .error-branding {
163 .error-branding {
164 color: @grey4;
164 color: @grey4;
165 font-weight: @text-semibold-weight;
165 font-weight: @text-semibold-weight;
166 font-family: @text-semibold;
166 font-family: @text-semibold;
167 }
167 }
168
168
169 .error_message {
169 .error_message {
170 font-family: @text-regular;
170 font-family: @text-regular;
171 }
171 }
172
172
173 .sidebar {
173 .sidebar {
174 min-height: 275px;
174 min-height: 275px;
175 margin: 0;
175 margin: 0;
176 padding: 0 0 @sidebarpadding @sidebarpadding;
176 padding: 0 0 @sidebarpadding @sidebarpadding;
177 border: none;
177 border: none;
178 }
178 }
179
179
180 .main-content {
180 .main-content {
181 position: relative;
181 position: relative;
182 margin: 0 @sidebarpadding @sidebarpadding;
182 margin: 0 @sidebarpadding @sidebarpadding;
183 padding: 0 0 0 @sidebarpadding;
183 padding: 0 0 0 @sidebarpadding;
184 border-left: @border-thickness solid @grey5;
184 border-left: @border-thickness solid @grey5;
185
185
186 @media (max-width:767px) {
186 @media (max-width:767px) {
187 clear: both;
187 clear: both;
188 width: 100%;
188 width: 100%;
189 margin: 0;
189 margin: 0;
190 border: none;
190 border: none;
191 }
191 }
192 }
192 }
193
193
194 .inner-column {
194 .inner-column {
195 float: left;
195 float: left;
196 width: 29.75%;
196 width: 29.75%;
197 min-height: 150px;
197 min-height: 150px;
198 margin: @sidebarpadding 2% 0 0;
198 margin: @sidebarpadding 2% 0 0;
199 padding: 0 2% 0 0;
199 padding: 0 2% 0 0;
200 border-right: @border-thickness solid @grey5;
200 border-right: @border-thickness solid @grey5;
201
201
202 @media (max-width:767px) {
202 @media (max-width:767px) {
203 clear: both;
203 clear: both;
204 width: 100%;
204 width: 100%;
205 border: none;
205 border: none;
206 }
206 }
207
207
208 ul {
208 ul {
209 padding-left: 1.25em;
209 padding-left: 1.25em;
210 }
210 }
211
211
212 &:last-child {
212 &:last-child {
213 margin: @sidebarpadding 0 0;
213 margin: @sidebarpadding 0 0;
214 border: none;
214 border: none;
215 }
215 }
216
216
217 h4 {
217 h4 {
218 margin: 0 0 @padding;
218 margin: 0 0 @padding;
219 font-weight: @text-semibold-weight;
219 font-weight: @text-semibold-weight;
220 font-family: @text-semibold;
220 font-family: @text-semibold;
221 }
221 }
222 }
222 }
223 }
223 }
224 .error-page-logo {
224 .error-page-logo {
225 width: 130px;
225 width: 130px;
226 height: 160px;
226 height: 160px;
227 }
227 }
228
228
229 // HEADER
229 // HEADER
230 .header {
230 .header {
231
231
232 // TODO: johbo: Fix login pages, so that they work without a min-height
232 // TODO: johbo: Fix login pages, so that they work without a min-height
233 // for the header and then remove the min-height. I chose a smaller value
233 // for the header and then remove the min-height. I chose a smaller value
234 // intentionally here to avoid rendering issues in the main navigation.
234 // intentionally here to avoid rendering issues in the main navigation.
235 min-height: 49px;
235 min-height: 49px;
236
236
237 position: relative;
237 position: relative;
238 vertical-align: bottom;
238 vertical-align: bottom;
239 padding: 0 @header-padding;
239 padding: 0 @header-padding;
240 background-color: @grey2;
240 background-color: @grey2;
241 color: @grey5;
241 color: @grey5;
242
242
243 .title {
243 .title {
244 overflow: visible;
244 overflow: visible;
245 }
245 }
246
246
247 &:before,
247 &:before,
248 &:after {
248 &:after {
249 content: "";
249 content: "";
250 clear: both;
250 clear: both;
251 width: 100%;
251 width: 100%;
252 }
252 }
253
253
254 // TODO: johbo: Avoids breaking "Repositories" chooser
254 // TODO: johbo: Avoids breaking "Repositories" chooser
255 .select2-container .select2-choice .select2-arrow {
255 .select2-container .select2-choice .select2-arrow {
256 display: none;
256 display: none;
257 }
257 }
258 }
258 }
259
259
260 #header-inner {
260 #header-inner {
261 &.title {
261 &.title {
262 margin: 0;
262 margin: 0;
263 }
263 }
264 &:before,
264 &:before,
265 &:after {
265 &:after {
266 content: "";
266 content: "";
267 clear: both;
267 clear: both;
268 }
268 }
269 }
269 }
270
270
271 // Gists
271 // Gists
272 #files_data {
272 #files_data {
273 clear: both; //for firefox
273 clear: both; //for firefox
274 }
274 }
275 #gistid {
275 #gistid {
276 margin-right: @padding;
276 margin-right: @padding;
277 }
277 }
278
278
279 // Global Settings Editor
279 // Global Settings Editor
280 .textarea.editor {
280 .textarea.editor {
281 float: left;
281 float: left;
282 position: relative;
282 position: relative;
283 max-width: @texteditor-width;
283 max-width: @texteditor-width;
284
284
285 select {
285 select {
286 position: absolute;
286 position: absolute;
287 top:10px;
287 top:10px;
288 right:0;
288 right:0;
289 }
289 }
290
290
291 .CodeMirror {
291 .CodeMirror {
292 margin: 0;
292 margin: 0;
293 }
293 }
294
294
295 .help-block {
295 .help-block {
296 margin: 0 0 @padding;
296 margin: 0 0 @padding;
297 padding:.5em;
297 padding:.5em;
298 background-color: @grey6;
298 background-color: @grey6;
299 &.pre-formatting {
299 &.pre-formatting {
300 white-space: pre;
300 white-space: pre;
301 }
301 }
302 }
302 }
303 }
303 }
304
304
305 ul.auth_plugins {
305 ul.auth_plugins {
306 margin: @padding 0 @padding @legend-width;
306 margin: @padding 0 @padding @legend-width;
307 padding: 0;
307 padding: 0;
308
308
309 li {
309 li {
310 margin-bottom: @padding;
310 margin-bottom: @padding;
311 line-height: 1em;
311 line-height: 1em;
312 list-style-type: none;
312 list-style-type: none;
313
313
314 .auth_buttons .btn {
314 .auth_buttons .btn {
315 margin-right: @padding;
315 margin-right: @padding;
316 }
316 }
317
317
318 }
318 }
319 }
319 }
320
320
321
321
322 // My Account PR list
322 // My Account PR list
323
323
324 #show_closed {
324 #show_closed {
325 margin: 0 1em 0 0;
325 margin: 0 1em 0 0;
326 }
326 }
327
327
328 .pullrequestlist {
328 .pullrequestlist {
329 .closed {
329 .closed {
330 background-color: @grey6;
330 background-color: @grey6;
331 }
331 }
332 .td-status {
332 .td-status {
333 padding-left: .5em;
333 padding-left: .5em;
334 }
334 }
335 .log-container .truncate {
335 .log-container .truncate {
336 height: 2.75em;
336 height: 2.75em;
337 white-space: pre-line;
337 white-space: pre-line;
338 }
338 }
339 table.rctable .user {
339 table.rctable .user {
340 padding-left: 0;
340 padding-left: 0;
341 }
341 }
342 table.rctable {
342 table.rctable {
343 td.td-description,
343 td.td-description,
344 .rc-user {
344 .rc-user {
345 min-width: auto;
345 min-width: auto;
346 }
346 }
347 }
347 }
348 }
348 }
349
349
350 // Pull Requests
350 // Pull Requests
351
351
352 .pullrequests_section_head {
352 .pullrequests_section_head {
353 display: block;
353 display: block;
354 clear: both;
354 clear: both;
355 margin: @padding 0;
355 margin: @padding 0;
356 font-weight: @text-bold-weight;
356 font-weight: @text-bold-weight;
357 font-family: @text-bold;
357 font-family: @text-bold;
358 }
358 }
359
359
360 .pr-origininfo, .pr-targetinfo {
360 .pr-origininfo, .pr-targetinfo {
361 position: relative;
361 position: relative;
362
362
363 .tag {
363 .tag {
364 display: inline-block;
364 display: inline-block;
365 margin: 0 1em .5em 0;
365 margin: 0 1em .5em 0;
366 }
366 }
367
367
368 .clone-url {
368 .clone-url {
369 display: inline-block;
369 display: inline-block;
370 margin: 0 0 .5em 0;
370 margin: 0 0 .5em 0;
371 padding: 0;
371 padding: 0;
372 line-height: 1.2em;
372 line-height: 1.2em;
373 }
373 }
374 }
374 }
375
375
376 .pr-mergeinfo {
376 .pr-mergeinfo {
377 min-width: 95% !important;
377 min-width: 95% !important;
378 padding: 0 !important;
378 padding: 0 !important;
379 border: 0;
379 border: 0;
380 }
380 }
381 .pr-mergeinfo-copy {
381 .pr-mergeinfo-copy {
382 padding: 0 0;
382 padding: 0 0;
383 }
383 }
384
384
385 .pr-pullinfo {
385 .pr-pullinfo {
386 min-width: 95% !important;
386 min-width: 95% !important;
387 padding: 0 !important;
387 padding: 0 !important;
388 border: 0;
388 border: 0;
389 }
389 }
390 .pr-pullinfo-copy {
390 .pr-pullinfo-copy {
391 padding: 0 0;
391 padding: 0 0;
392 }
392 }
393
393
394
394
395 #pr-title-input {
395 #pr-title-input {
396 width: 72%;
396 width: 72%;
397 font-size: 1em;
397 font-size: 1em;
398 margin: 0;
398 margin: 0;
399 padding: 0 0 0 @padding/4;
399 padding: 0 0 0 @padding/4;
400 line-height: 1.7em;
400 line-height: 1.7em;
401 color: @text-color;
401 color: @text-color;
402 letter-spacing: .02em;
402 letter-spacing: .02em;
403 font-weight: @text-bold-weight;
403 font-weight: @text-bold-weight;
404 font-family: @text-bold;
404 font-family: @text-bold;
405 }
405 }
406
406
407 #pullrequest_title {
407 #pullrequest_title {
408 width: 100%;
408 width: 100%;
409 box-sizing: border-box;
409 box-sizing: border-box;
410 }
410 }
411
411
412 #pr_open_message {
412 #pr_open_message {
413 border: @border-thickness solid #fff;
413 border: @border-thickness solid #fff;
414 border-radius: @border-radius;
414 border-radius: @border-radius;
415 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
415 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
416 text-align: left;
416 text-align: left;
417 overflow: hidden;
417 overflow: hidden;
418 }
418 }
419
419
420 .pr-submit-button {
420 .pr-submit-button {
421 float: right;
421 float: right;
422 margin: 0 0 0 5px;
422 margin: 0 0 0 5px;
423 }
423 }
424
424
425 .pr-spacing-container {
425 .pr-spacing-container {
426 padding: 20px;
426 padding: 20px;
427 clear: both
427 clear: both
428 }
428 }
429
429
430 #pr-description-input {
430 #pr-description-input {
431 margin-bottom: 0;
431 margin-bottom: 0;
432 }
432 }
433
433
434 .pr-description-label {
434 .pr-description-label {
435 vertical-align: top;
435 vertical-align: top;
436 }
436 }
437
437
438 .perms_section_head {
438 .perms_section_head {
439 min-width: 625px;
439 min-width: 625px;
440
440
441 h2 {
441 h2 {
442 margin-bottom: 0;
442 margin-bottom: 0;
443 }
443 }
444
444
445 .label-checkbox {
445 .label-checkbox {
446 float: left;
446 float: left;
447 }
447 }
448
448
449 &.field {
449 &.field {
450 margin: @space 0 @padding;
450 margin: @space 0 @padding;
451 }
451 }
452
452
453 &:first-child.field {
453 &:first-child.field {
454 margin-top: 0;
454 margin-top: 0;
455
455
456 .label {
456 .label {
457 margin-top: 0;
457 margin-top: 0;
458 padding-top: 0;
458 padding-top: 0;
459 }
459 }
460
460
461 .radios {
461 .radios {
462 padding-top: 0;
462 padding-top: 0;
463 }
463 }
464 }
464 }
465
465
466 .radios {
466 .radios {
467 position: relative;
467 position: relative;
468 width: 505px;
468 width: 505px;
469 }
469 }
470 }
470 }
471
471
472 //--- MODULES ------------------//
472 //--- MODULES ------------------//
473
473
474
474
475 // Server Announcement
475 // Server Announcement
476 #server-announcement {
476 #server-announcement {
477 width: 95%;
477 width: 95%;
478 margin: @padding auto;
478 margin: @padding auto;
479 padding: @padding;
479 padding: @padding;
480 border-width: 2px;
480 border-width: 2px;
481 border-style: solid;
481 border-style: solid;
482 .border-radius(2px);
482 .border-radius(2px);
483 font-weight: @text-bold-weight;
483 font-weight: @text-bold-weight;
484 font-family: @text-bold;
484 font-family: @text-bold;
485
485
486 &.info { border-color: @alert4; background-color: @alert4-inner; }
486 &.info { border-color: @alert4; background-color: @alert4-inner; }
487 &.warning { border-color: @alert3; background-color: @alert3-inner; }
487 &.warning { border-color: @alert3; background-color: @alert3-inner; }
488 &.error { border-color: @alert2; background-color: @alert2-inner; }
488 &.error { border-color: @alert2; background-color: @alert2-inner; }
489 &.success { border-color: @alert1; background-color: @alert1-inner; }
489 &.success { border-color: @alert1; background-color: @alert1-inner; }
490 &.neutral { border-color: @grey3; background-color: @grey6; }
490 &.neutral { border-color: @grey3; background-color: @grey6; }
491 }
491 }
492
492
493 // Fixed Sidebar Column
493 // Fixed Sidebar Column
494 .sidebar-col-wrapper {
494 .sidebar-col-wrapper {
495 padding-left: @sidebar-all-width;
495 padding-left: @sidebar-all-width;
496
496
497 .sidebar {
497 .sidebar {
498 width: @sidebar-width;
498 width: @sidebar-width;
499 margin-left: -@sidebar-all-width;
499 margin-left: -@sidebar-all-width;
500 }
500 }
501 }
501 }
502
502
503 .sidebar-col-wrapper.scw-small {
503 .sidebar-col-wrapper.scw-small {
504 padding-left: @sidebar-small-all-width;
504 padding-left: @sidebar-small-all-width;
505
505
506 .sidebar {
506 .sidebar {
507 width: @sidebar-small-width;
507 width: @sidebar-small-width;
508 margin-left: -@sidebar-small-all-width;
508 margin-left: -@sidebar-small-all-width;
509 }
509 }
510 }
510 }
511
511
512
512
513 // FOOTER
513 // FOOTER
514 #footer {
514 #footer {
515 padding: 0;
515 padding: 0;
516 text-align: center;
516 text-align: center;
517 vertical-align: middle;
517 vertical-align: middle;
518 color: @grey2;
518 color: @grey2;
519 background-color: @grey6;
519 background-color: @grey6;
520
520
521 p {
521 p {
522 margin: 0;
522 margin: 0;
523 padding: 1em;
523 padding: 1em;
524 line-height: 1em;
524 line-height: 1em;
525 }
525 }
526
526
527 .server-instance { //server instance
527 .server-instance { //server instance
528 display: none;
528 display: none;
529 }
529 }
530
530
531 .title {
531 .title {
532 float: none;
532 float: none;
533 margin: 0 auto;
533 margin: 0 auto;
534 }
534 }
535 }
535 }
536
536
537 button.close {
537 button.close {
538 padding: 0;
538 padding: 0;
539 cursor: pointer;
539 cursor: pointer;
540 background: transparent;
540 background: transparent;
541 border: 0;
541 border: 0;
542 .box-shadow(none);
542 .box-shadow(none);
543 -webkit-appearance: none;
543 -webkit-appearance: none;
544 }
544 }
545
545
546 .close {
546 .close {
547 float: right;
547 float: right;
548 font-size: 21px;
548 font-size: 21px;
549 font-family: @text-bootstrap;
549 font-family: @text-bootstrap;
550 line-height: 1em;
550 line-height: 1em;
551 font-weight: bold;
551 font-weight: bold;
552 color: @grey2;
552 color: @grey2;
553
553
554 &:hover,
554 &:hover,
555 &:focus {
555 &:focus {
556 color: @grey1;
556 color: @grey1;
557 text-decoration: none;
557 text-decoration: none;
558 cursor: pointer;
558 cursor: pointer;
559 }
559 }
560 }
560 }
561
561
562 // GRID
562 // GRID
563 .sorting,
563 .sorting,
564 .sorting_desc,
564 .sorting_desc,
565 .sorting_asc {
565 .sorting_asc {
566 cursor: pointer;
566 cursor: pointer;
567 }
567 }
568 .sorting_desc:after {
568 .sorting_desc:after {
569 content: "\00A0\25B2";
569 content: "\00A0\25B2";
570 font-size: .75em;
570 font-size: .75em;
571 }
571 }
572 .sorting_asc:after {
572 .sorting_asc:after {
573 content: "\00A0\25BC";
573 content: "\00A0\25BC";
574 font-size: .68em;
574 font-size: .68em;
575 }
575 }
576
576
577
577
578 .user_auth_tokens {
578 .user_auth_tokens {
579
579
580 &.truncate {
580 &.truncate {
581 white-space: nowrap;
581 white-space: nowrap;
582 overflow: hidden;
582 overflow: hidden;
583 text-overflow: ellipsis;
583 text-overflow: ellipsis;
584 }
584 }
585
585
586 .fields .field .input {
586 .fields .field .input {
587 margin: 0;
587 margin: 0;
588 }
588 }
589
589
590 input#description {
590 input#description {
591 width: 100px;
591 width: 100px;
592 margin: 0;
592 margin: 0;
593 }
593 }
594
594
595 .drop-menu {
595 .drop-menu {
596 // TODO: johbo: Remove this, should work out of the box when
596 // TODO: johbo: Remove this, should work out of the box when
597 // having multiple inputs inline
597 // having multiple inputs inline
598 margin: 0 0 0 5px;
598 margin: 0 0 0 5px;
599 }
599 }
600 }
600 }
601 #user_list_table {
601 #user_list_table {
602 .closed {
602 .closed {
603 background-color: @grey6;
603 background-color: @grey6;
604 }
604 }
605 }
605 }
606
606
607
607
608 input {
608 input {
609 &.disabled {
609 &.disabled {
610 opacity: .5;
610 opacity: .5;
611 }
611 }
612 }
612 }
613
613
614 // remove extra padding in firefox
614 // remove extra padding in firefox
615 input::-moz-focus-inner { border:0; padding:0 }
615 input::-moz-focus-inner { border:0; padding:0 }
616
616
617 .adjacent input {
617 .adjacent input {
618 margin-bottom: @padding;
618 margin-bottom: @padding;
619 }
619 }
620
620
621 .permissions_boxes {
621 .permissions_boxes {
622 display: block;
622 display: block;
623 }
623 }
624
624
625 //FORMS
625 //FORMS
626
626
627 .medium-inline,
627 .medium-inline,
628 input#description.medium-inline {
628 input#description.medium-inline {
629 display: inline;
629 display: inline;
630 width: @medium-inline-input-width;
630 width: @medium-inline-input-width;
631 min-width: 100px;
631 min-width: 100px;
632 }
632 }
633
633
634 select {
634 select {
635 //reset
635 //reset
636 -webkit-appearance: none;
636 -webkit-appearance: none;
637 -moz-appearance: none;
637 -moz-appearance: none;
638
638
639 display: inline-block;
639 display: inline-block;
640 height: 28px;
640 height: 28px;
641 width: auto;
641 width: auto;
642 margin: 0 @padding @padding 0;
642 margin: 0 @padding @padding 0;
643 padding: 0 18px 0 8px;
643 padding: 0 18px 0 8px;
644 line-height:1em;
644 line-height:1em;
645 font-size: @basefontsize;
645 font-size: @basefontsize;
646 border: @border-thickness solid @rcblue;
646 border: @border-thickness solid @rcblue;
647 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
647 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
648 color: @rcblue;
648 color: @rcblue;
649
649
650 &:after {
650 &:after {
651 content: "\00A0\25BE";
651 content: "\00A0\25BE";
652 }
652 }
653
653
654 &:focus {
654 &:focus {
655 outline: none;
655 outline: none;
656 }
656 }
657 }
657 }
658
658
659 option {
659 option {
660 &:focus {
660 &:focus {
661 outline: none;
661 outline: none;
662 }
662 }
663 }
663 }
664
664
665 input,
665 input,
666 textarea {
666 textarea {
667 padding: @input-padding;
667 padding: @input-padding;
668 border: @input-border-thickness solid @border-highlight-color;
668 border: @input-border-thickness solid @border-highlight-color;
669 .border-radius (@border-radius);
669 .border-radius (@border-radius);
670 font-family: @text-light;
670 font-family: @text-light;
671 font-size: @basefontsize;
671 font-size: @basefontsize;
672
672
673 &.input-sm {
673 &.input-sm {
674 padding: 5px;
674 padding: 5px;
675 }
675 }
676
676
677 &#description {
677 &#description {
678 min-width: @input-description-minwidth;
678 min-width: @input-description-minwidth;
679 min-height: 1em;
679 min-height: 1em;
680 padding: 10px;
680 padding: 10px;
681 }
681 }
682 }
682 }
683
683
684 .field-sm {
684 .field-sm {
685 input,
685 input,
686 textarea {
686 textarea {
687 padding: 5px;
687 padding: 5px;
688 }
688 }
689 }
689 }
690
690
691 textarea {
691 textarea {
692 display: block;
692 display: block;
693 clear: both;
693 clear: both;
694 width: 100%;
694 width: 100%;
695 min-height: 100px;
695 min-height: 100px;
696 margin-bottom: @padding;
696 margin-bottom: @padding;
697 .box-sizing(border-box);
697 .box-sizing(border-box);
698 overflow: auto;
698 overflow: auto;
699 }
699 }
700
700
701 label {
701 label {
702 font-family: @text-light;
702 font-family: @text-light;
703 }
703 }
704
704
705 // GRAVATARS
705 // GRAVATARS
706 // centers gravatar on username to the right
706 // centers gravatar on username to the right
707
707
708 .gravatar {
708 .gravatar {
709 display: inline;
709 display: inline;
710 min-width: 16px;
710 min-width: 16px;
711 min-height: 16px;
711 min-height: 16px;
712 margin: -5px 0;
712 margin: -5px 0;
713 padding: 0;
713 padding: 0;
714 line-height: 1em;
714 line-height: 1em;
715 border: 1px solid @grey4;
715 border: 1px solid @grey4;
716 box-sizing: content-box;
716 box-sizing: content-box;
717
717
718 &.gravatar-large {
718 &.gravatar-large {
719 margin: -0.5em .25em -0.5em 0;
719 margin: -0.5em .25em -0.5em 0;
720 }
720 }
721
721
722 & + .user {
722 & + .user {
723 display: inline;
723 display: inline;
724 margin: 0;
724 margin: 0;
725 padding: 0 0 0 .17em;
725 padding: 0 0 0 .17em;
726 line-height: 1em;
726 line-height: 1em;
727 }
727 }
728 }
728 }
729
729
730 .user-inline-data {
730 .user-inline-data {
731 display: inline-block;
731 display: inline-block;
732 float: left;
732 float: left;
733 padding-left: .5em;
733 padding-left: .5em;
734 line-height: 1.3em;
734 line-height: 1.3em;
735 }
735 }
736
736
737 .rc-user { // gravatar + user wrapper
737 .rc-user { // gravatar + user wrapper
738 float: left;
738 float: left;
739 position: relative;
739 position: relative;
740 min-width: 100px;
740 min-width: 100px;
741 max-width: 200px;
741 max-width: 200px;
742 min-height: (@gravatar-size + @border-thickness * 2); // account for border
742 min-height: (@gravatar-size + @border-thickness * 2); // account for border
743 display: block;
743 display: block;
744 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
744 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
745
745
746
746
747 .gravatar {
747 .gravatar {
748 display: block;
748 display: block;
749 position: absolute;
749 position: absolute;
750 top: 0;
750 top: 0;
751 left: 0;
751 left: 0;
752 min-width: @gravatar-size;
752 min-width: @gravatar-size;
753 min-height: @gravatar-size;
753 min-height: @gravatar-size;
754 margin: 0;
754 margin: 0;
755 }
755 }
756
756
757 .user {
757 .user {
758 display: block;
758 display: block;
759 max-width: 175px;
759 max-width: 175px;
760 padding-top: 2px;
760 padding-top: 2px;
761 overflow: hidden;
761 overflow: hidden;
762 text-overflow: ellipsis;
762 text-overflow: ellipsis;
763 }
763 }
764 }
764 }
765
765
766 .gist-gravatar,
766 .gist-gravatar,
767 .journal_container {
767 .journal_container {
768 .gravatar-large {
768 .gravatar-large {
769 margin: 0 .5em -10px 0;
769 margin: 0 .5em -10px 0;
770 }
770 }
771 }
771 }
772
772
773
773
774 // ADMIN SETTINGS
774 // ADMIN SETTINGS
775
775
776 // Tag Patterns
776 // Tag Patterns
777 .tag_patterns {
777 .tag_patterns {
778 .tag_input {
778 .tag_input {
779 margin-bottom: @padding;
779 margin-bottom: @padding;
780 }
780 }
781 }
781 }
782
782
783 .locked_input {
783 .locked_input {
784 position: relative;
784 position: relative;
785
785
786 input {
786 input {
787 display: inline;
787 display: inline;
788 margin: 3px 5px 0px 0px;
788 margin: 3px 5px 0px 0px;
789 }
789 }
790
790
791 br {
791 br {
792 display: none;
792 display: none;
793 }
793 }
794
794
795 .error-message {
795 .error-message {
796 float: left;
796 float: left;
797 width: 100%;
797 width: 100%;
798 }
798 }
799
799
800 .lock_input_button {
800 .lock_input_button {
801 display: inline;
801 display: inline;
802 }
802 }
803
803
804 .help-block {
804 .help-block {
805 clear: both;
805 clear: both;
806 }
806 }
807 }
807 }
808
808
809 // Notifications
809 // Notifications
810
810
811 .notifications_buttons {
811 .notifications_buttons {
812 margin: 0 0 @space 0;
812 margin: 0 0 @space 0;
813 padding: 0;
813 padding: 0;
814
814
815 .btn {
815 .btn {
816 display: inline-block;
816 display: inline-block;
817 }
817 }
818 }
818 }
819
819
820 .notification-list {
820 .notification-list {
821
821
822 div {
822 div {
823 display: inline-block;
823 display: inline-block;
824 vertical-align: middle;
824 vertical-align: middle;
825 }
825 }
826
826
827 .container {
827 .container {
828 display: block;
828 display: block;
829 margin: 0 0 @padding 0;
829 margin: 0 0 @padding 0;
830 }
830 }
831
831
832 .delete-notifications {
832 .delete-notifications {
833 margin-left: @padding;
833 margin-left: @padding;
834 text-align: right;
834 text-align: right;
835 cursor: pointer;
835 cursor: pointer;
836 }
836 }
837
837
838 .read-notifications {
838 .read-notifications {
839 margin-left: @padding/2;
839 margin-left: @padding/2;
840 text-align: right;
840 text-align: right;
841 width: 35px;
841 width: 35px;
842 cursor: pointer;
842 cursor: pointer;
843 }
843 }
844
844
845 .icon-minus-sign {
845 .icon-minus-sign {
846 color: @alert2;
846 color: @alert2;
847 }
847 }
848
848
849 .icon-ok-sign {
849 .icon-ok-sign {
850 color: @alert1;
850 color: @alert1;
851 }
851 }
852 }
852 }
853
853
854 .user_settings {
854 .user_settings {
855 float: left;
855 float: left;
856 clear: both;
856 clear: both;
857 display: block;
857 display: block;
858 width: 100%;
858 width: 100%;
859
859
860 .gravatar_box {
860 .gravatar_box {
861 margin-bottom: @padding;
861 margin-bottom: @padding;
862
862
863 &:after {
863 &:after {
864 content: " ";
864 content: " ";
865 clear: both;
865 clear: both;
866 width: 100%;
866 width: 100%;
867 }
867 }
868 }
868 }
869
869
870 .fields .field {
870 .fields .field {
871 clear: both;
871 clear: both;
872 }
872 }
873 }
873 }
874
874
875 .advanced_settings {
875 .advanced_settings {
876 margin-bottom: @space;
876 margin-bottom: @space;
877
877
878 .help-block {
878 .help-block {
879 margin-left: 0;
879 margin-left: 0;
880 }
880 }
881
881
882 button + .help-block {
882 button + .help-block {
883 margin-top: @padding;
883 margin-top: @padding;
884 }
884 }
885 }
885 }
886
886
887 // admin settings radio buttons and labels
887 // admin settings radio buttons and labels
888 .label-2 {
888 .label-2 {
889 float: left;
889 float: left;
890 width: @label2-width;
890 width: @label2-width;
891
891
892 label {
892 label {
893 color: @grey1;
893 color: @grey1;
894 }
894 }
895 }
895 }
896 .checkboxes {
896 .checkboxes {
897 float: left;
897 float: left;
898 width: @checkboxes-width;
898 width: @checkboxes-width;
899 margin-bottom: @padding;
899 margin-bottom: @padding;
900
900
901 .checkbox {
901 .checkbox {
902 width: 100%;
902 width: 100%;
903
903
904 label {
904 label {
905 margin: 0;
905 margin: 0;
906 padding: 0;
906 padding: 0;
907 }
907 }
908 }
908 }
909
909
910 .checkbox + .checkbox {
910 .checkbox + .checkbox {
911 display: inline-block;
911 display: inline-block;
912 }
912 }
913
913
914 label {
914 label {
915 margin-right: 1em;
915 margin-right: 1em;
916 }
916 }
917 }
917 }
918
918
919 // CHANGELOG
919 // CHANGELOG
920 .container_header {
920 .container_header {
921 float: left;
921 float: left;
922 display: block;
922 display: block;
923 width: 100%;
923 width: 100%;
924 margin: @padding 0 @padding;
924 margin: @padding 0 @padding;
925
925
926 #filter_changelog {
926 #filter_changelog {
927 float: left;
927 float: left;
928 margin-right: @padding;
928 margin-right: @padding;
929 }
929 }
930
930
931 .breadcrumbs_light {
931 .breadcrumbs_light {
932 display: inline-block;
932 display: inline-block;
933 }
933 }
934 }
934 }
935
935
936 .info_box {
936 .info_box {
937 float: right;
937 float: right;
938 }
938 }
939
939
940
940
941 #graph_nodes {
941 #graph_nodes {
942 padding-top: 43px;
942 padding-top: 43px;
943 }
943 }
944
944
945 #graph_content{
945 #graph_content{
946
946
947 // adjust for table headers so that graph renders properly
947 // adjust for table headers so that graph renders properly
948 // #graph_nodes padding - table cell padding
948 // #graph_nodes padding - table cell padding
949 padding-top: (@space - (@basefontsize * 2.4));
949 padding-top: (@space - (@basefontsize * 2.4));
950
950
951 &.graph_full_width {
951 &.graph_full_width {
952 width: 100%;
952 width: 100%;
953 max-width: 100%;
953 max-width: 100%;
954 }
954 }
955 }
955 }
956
956
957 #graph {
957 #graph {
958 .flag_status {
958 .flag_status {
959 margin: 0;
959 margin: 0;
960 }
960 }
961
961
962 .pagination-left {
962 .pagination-left {
963 float: left;
963 float: left;
964 clear: both;
964 clear: both;
965 }
965 }
966
966
967 .log-container {
967 .log-container {
968 max-width: 345px;
968 max-width: 345px;
969
969
970 .message{
970 .message{
971 max-width: 340px;
971 max-width: 340px;
972 }
972 }
973 }
973 }
974
974
975 .graph-col-wrapper {
975 .graph-col-wrapper {
976 padding-left: 110px;
976 padding-left: 110px;
977
977
978 #graph_nodes {
978 #graph_nodes {
979 width: 100px;
979 width: 100px;
980 margin-left: -110px;
980 margin-left: -110px;
981 float: left;
981 float: left;
982 clear: left;
982 clear: left;
983 }
983 }
984 }
984 }
985
985
986 .load-more-commits {
986 .load-more-commits {
987 text-align: center;
987 text-align: center;
988 }
988 }
989 .load-more-commits:hover {
989 .load-more-commits:hover {
990 background-color: @grey7;
990 background-color: @grey7;
991 }
991 }
992 .load-more-commits {
992 .load-more-commits {
993 a {
993 a {
994 display: block;
994 display: block;
995 }
995 }
996 }
996 }
997 }
997 }
998
998
999 #filter_changelog {
999 #filter_changelog {
1000 float: left;
1000 float: left;
1001 }
1001 }
1002
1002
1003
1003
1004 //--- THEME ------------------//
1004 //--- THEME ------------------//
1005
1005
1006 #logo {
1006 #logo {
1007 float: left;
1007 float: left;
1008 margin: 9px 0 0 0;
1008 margin: 9px 0 0 0;
1009
1009
1010 .header {
1010 .header {
1011 background-color: transparent;
1011 background-color: transparent;
1012 }
1012 }
1013
1013
1014 a {
1014 a {
1015 display: inline-block;
1015 display: inline-block;
1016 }
1016 }
1017
1017
1018 img {
1018 img {
1019 height:30px;
1019 height:30px;
1020 }
1020 }
1021 }
1021 }
1022
1022
1023 .logo-wrapper {
1023 .logo-wrapper {
1024 float:left;
1024 float:left;
1025 }
1025 }
1026
1026
1027 .branding{
1027 .branding{
1028 float: left;
1028 float: left;
1029 padding: 9px 2px;
1029 padding: 9px 2px;
1030 line-height: 1em;
1030 line-height: 1em;
1031 font-size: @navigation-fontsize;
1031 font-size: @navigation-fontsize;
1032 }
1032 }
1033
1033
1034 img {
1034 img {
1035 border: none;
1035 border: none;
1036 outline: none;
1036 outline: none;
1037 }
1037 }
1038 user-profile-header
1038 user-profile-header
1039 label {
1039 label {
1040
1040
1041 input[type="checkbox"] {
1041 input[type="checkbox"] {
1042 margin-right: 1em;
1042 margin-right: 1em;
1043 }
1043 }
1044 input[type="radio"] {
1044 input[type="radio"] {
1045 margin-right: 1em;
1045 margin-right: 1em;
1046 }
1046 }
1047 }
1047 }
1048
1048
1049 .flag_status {
1049 .flag_status {
1050 margin: 2px 8px 6px 2px;
1050 margin: 2px 8px 6px 2px;
1051 &.under_review {
1051 &.under_review {
1052 .circle(5px, @alert3);
1052 .circle(5px, @alert3);
1053 }
1053 }
1054 &.approved {
1054 &.approved {
1055 .circle(5px, @alert1);
1055 .circle(5px, @alert1);
1056 }
1056 }
1057 &.rejected,
1057 &.rejected,
1058 &.forced_closed{
1058 &.forced_closed{
1059 .circle(5px, @alert2);
1059 .circle(5px, @alert2);
1060 }
1060 }
1061 &.not_reviewed {
1061 &.not_reviewed {
1062 .circle(5px, @grey5);
1062 .circle(5px, @grey5);
1063 }
1063 }
1064 }
1064 }
1065
1065
1066 .flag_status_comment_box {
1066 .flag_status_comment_box {
1067 margin: 5px 6px 0px 2px;
1067 margin: 5px 6px 0px 2px;
1068 }
1068 }
1069 .test_pattern_preview {
1069 .test_pattern_preview {
1070 margin: @space 0;
1070 margin: @space 0;
1071
1071
1072 p {
1072 p {
1073 margin-bottom: 0;
1073 margin-bottom: 0;
1074 border-bottom: @border-thickness solid @border-default-color;
1074 border-bottom: @border-thickness solid @border-default-color;
1075 color: @grey3;
1075 color: @grey3;
1076 }
1076 }
1077
1077
1078 .btn {
1078 .btn {
1079 margin-bottom: @padding;
1079 margin-bottom: @padding;
1080 }
1080 }
1081 }
1081 }
1082 #test_pattern_result {
1082 #test_pattern_result {
1083 display: none;
1083 display: none;
1084 &:extend(pre);
1084 &:extend(pre);
1085 padding: .9em;
1085 padding: .9em;
1086 color: @grey3;
1086 color: @grey3;
1087 background-color: @grey7;
1087 background-color: @grey7;
1088 border-right: @border-thickness solid @border-default-color;
1088 border-right: @border-thickness solid @border-default-color;
1089 border-bottom: @border-thickness solid @border-default-color;
1089 border-bottom: @border-thickness solid @border-default-color;
1090 border-left: @border-thickness solid @border-default-color;
1090 border-left: @border-thickness solid @border-default-color;
1091 }
1091 }
1092
1092
1093 #repo_vcs_settings {
1093 #repo_vcs_settings {
1094 #inherit_overlay_vcs_default {
1094 #inherit_overlay_vcs_default {
1095 display: none;
1095 display: none;
1096 }
1096 }
1097 #inherit_overlay_vcs_custom {
1097 #inherit_overlay_vcs_custom {
1098 display: custom;
1098 display: custom;
1099 }
1099 }
1100 &.inherited {
1100 &.inherited {
1101 #inherit_overlay_vcs_default {
1101 #inherit_overlay_vcs_default {
1102 display: block;
1102 display: block;
1103 }
1103 }
1104 #inherit_overlay_vcs_custom {
1104 #inherit_overlay_vcs_custom {
1105 display: none;
1105 display: none;
1106 }
1106 }
1107 }
1107 }
1108 }
1108 }
1109
1109
1110 .issue-tracker-link {
1110 .issue-tracker-link {
1111 color: @rcblue;
1111 color: @rcblue;
1112 }
1112 }
1113
1113
1114 // Issue Tracker Table Show/Hide
1114 // Issue Tracker Table Show/Hide
1115 #repo_issue_tracker {
1115 #repo_issue_tracker {
1116 #inherit_overlay {
1116 #inherit_overlay {
1117 display: none;
1117 display: none;
1118 }
1118 }
1119 #custom_overlay {
1119 #custom_overlay {
1120 display: custom;
1120 display: custom;
1121 }
1121 }
1122 &.inherited {
1122 &.inherited {
1123 #inherit_overlay {
1123 #inherit_overlay {
1124 display: block;
1124 display: block;
1125 }
1125 }
1126 #custom_overlay {
1126 #custom_overlay {
1127 display: none;
1127 display: none;
1128 }
1128 }
1129 }
1129 }
1130 }
1130 }
1131 table.issuetracker {
1131 table.issuetracker {
1132 &.readonly {
1132 &.readonly {
1133 tr, td {
1133 tr, td {
1134 color: @grey3;
1134 color: @grey3;
1135 }
1135 }
1136 }
1136 }
1137 .edit {
1137 .edit {
1138 display: none;
1138 display: none;
1139 }
1139 }
1140 .editopen {
1140 .editopen {
1141 .edit {
1141 .edit {
1142 display: inline;
1142 display: inline;
1143 }
1143 }
1144 .entry {
1144 .entry {
1145 display: none;
1145 display: none;
1146 }
1146 }
1147 }
1147 }
1148 tr td.td-action {
1148 tr td.td-action {
1149 min-width: 117px;
1149 min-width: 117px;
1150 }
1150 }
1151 td input {
1151 td input {
1152 max-width: none;
1152 max-width: none;
1153 min-width: 30px;
1153 min-width: 30px;
1154 width: 80%;
1154 width: 80%;
1155 }
1155 }
1156 .issuetracker_pref input {
1156 .issuetracker_pref input {
1157 width: 40%;
1157 width: 40%;
1158 }
1158 }
1159 input.edit_issuetracker_update {
1159 input.edit_issuetracker_update {
1160 margin-right: 0;
1160 margin-right: 0;
1161 width: auto;
1161 width: auto;
1162 }
1162 }
1163 }
1163 }
1164
1164
1165 table.integrations {
1165 table.integrations {
1166 .td-icon {
1166 .td-icon {
1167 width: 20px;
1167 width: 20px;
1168 .integration-icon {
1168 .integration-icon {
1169 height: 20px;
1169 height: 20px;
1170 width: 20px;
1170 width: 20px;
1171 }
1171 }
1172 }
1172 }
1173 }
1173 }
1174
1174
1175 .integrations {
1175 .integrations {
1176 a.integration-box {
1176 a.integration-box {
1177 color: @text-color;
1177 color: @text-color;
1178 &:hover {
1178 &:hover {
1179 .panel {
1179 .panel {
1180 background: #fbfbfb;
1180 background: #fbfbfb;
1181 }
1181 }
1182 }
1182 }
1183 .integration-icon {
1183 .integration-icon {
1184 width: 30px;
1184 width: 30px;
1185 height: 30px;
1185 height: 30px;
1186 margin-right: 20px;
1186 margin-right: 20px;
1187 float: left;
1187 float: left;
1188 }
1188 }
1189
1189
1190 .panel-body {
1190 .panel-body {
1191 padding: 10px;
1191 padding: 10px;
1192 }
1192 }
1193 .panel {
1193 .panel {
1194 margin-bottom: 10px;
1194 margin-bottom: 10px;
1195 }
1195 }
1196 h2 {
1196 h2 {
1197 display: inline-block;
1197 display: inline-block;
1198 margin: 0;
1198 margin: 0;
1199 min-width: 140px;
1199 min-width: 140px;
1200 }
1200 }
1201 }
1201 }
1202 a.integration-box.dummy-integration {
1202 a.integration-box.dummy-integration {
1203 color: @grey4
1203 color: @grey4
1204 }
1204 }
1205 }
1205 }
1206
1206
1207 //Permissions Settings
1207 //Permissions Settings
1208 #add_perm {
1208 #add_perm {
1209 margin: 0 0 @padding;
1209 margin: 0 0 @padding;
1210 cursor: pointer;
1210 cursor: pointer;
1211 }
1211 }
1212
1212
1213 .perm_ac {
1213 .perm_ac {
1214 input {
1214 input {
1215 width: 95%;
1215 width: 95%;
1216 }
1216 }
1217 }
1217 }
1218
1218
1219 .autocomplete-suggestions {
1219 .autocomplete-suggestions {
1220 width: auto !important; // overrides autocomplete.js
1220 width: auto !important; // overrides autocomplete.js
1221 margin: 0;
1221 margin: 0;
1222 border: @border-thickness solid @rcblue;
1222 border: @border-thickness solid @rcblue;
1223 border-radius: @border-radius;
1223 border-radius: @border-radius;
1224 color: @rcblue;
1224 color: @rcblue;
1225 background-color: white;
1225 background-color: white;
1226 }
1226 }
1227 .autocomplete-selected {
1227 .autocomplete-selected {
1228 background: #F0F0F0;
1228 background: #F0F0F0;
1229 }
1229 }
1230 .ac-container-wrap {
1230 .ac-container-wrap {
1231 margin: 0;
1231 margin: 0;
1232 padding: 8px;
1232 padding: 8px;
1233 border-bottom: @border-thickness solid @rclightblue;
1233 border-bottom: @border-thickness solid @rclightblue;
1234 list-style-type: none;
1234 list-style-type: none;
1235 cursor: pointer;
1235 cursor: pointer;
1236
1236
1237 &:hover {
1237 &:hover {
1238 background-color: @rclightblue;
1238 background-color: @rclightblue;
1239 }
1239 }
1240
1240
1241 img {
1241 img {
1242 height: @gravatar-size;
1242 height: @gravatar-size;
1243 width: @gravatar-size;
1243 width: @gravatar-size;
1244 margin-right: 1em;
1244 margin-right: 1em;
1245 }
1245 }
1246
1246
1247 strong {
1247 strong {
1248 font-weight: normal;
1248 font-weight: normal;
1249 }
1249 }
1250 }
1250 }
1251
1251
1252 // Settings Dropdown
1252 // Settings Dropdown
1253 .user-menu .container {
1253 .user-menu .container {
1254 padding: 0 4px;
1254 padding: 0 4px;
1255 margin: 0;
1255 margin: 0;
1256 }
1256 }
1257
1257
1258 .user-menu .gravatar {
1258 .user-menu .gravatar {
1259 cursor: pointer;
1259 cursor: pointer;
1260 }
1260 }
1261
1261
1262 .codeblock {
1262 .codeblock {
1263 margin-bottom: @padding;
1263 margin-bottom: @padding;
1264 clear: both;
1264 clear: both;
1265
1265
1266 .stats {
1266 .stats {
1267 overflow: hidden;
1267 overflow: hidden;
1268 }
1268 }
1269
1269
1270 .message{
1270 .message{
1271 textarea{
1271 textarea{
1272 margin: 0;
1272 margin: 0;
1273 }
1273 }
1274 }
1274 }
1275
1275
1276 .code-header {
1276 .code-header {
1277 .stats {
1277 .stats {
1278 line-height: 2em;
1278 line-height: 2em;
1279
1279
1280 .revision_id {
1280 .revision_id {
1281 margin-left: 0;
1281 margin-left: 0;
1282 }
1282 }
1283 .buttons {
1283 .buttons {
1284 padding-right: 0;
1284 padding-right: 0;
1285 }
1285 }
1286 }
1286 }
1287
1287
1288 .item{
1288 .item{
1289 margin-right: 0.5em;
1289 margin-right: 0.5em;
1290 }
1290 }
1291 }
1291 }
1292
1292
1293 #editor_container{
1293 #editor_container{
1294 position: relative;
1294 position: relative;
1295 margin: @padding;
1295 margin: @padding;
1296 }
1296 }
1297 }
1297 }
1298
1298
1299 #file_history_container {
1299 #file_history_container {
1300 display: none;
1300 display: none;
1301 }
1301 }
1302
1302
1303 .file-history-inner {
1303 .file-history-inner {
1304 margin-bottom: 10px;
1304 margin-bottom: 10px;
1305 }
1305 }
1306
1306
1307 // Pull Requests
1307 // Pull Requests
1308 .summary-details {
1308 .summary-details {
1309 width: 72%;
1309 width: 72%;
1310 }
1310 }
1311 .pr-summary {
1311 .pr-summary {
1312 border-bottom: @border-thickness solid @grey5;
1312 border-bottom: @border-thickness solid @grey5;
1313 margin-bottom: @space;
1313 margin-bottom: @space;
1314 }
1314 }
1315 .reviewers-title {
1315 .reviewers-title {
1316 width: 25%;
1316 width: 25%;
1317 min-width: 200px;
1317 min-width: 200px;
1318 }
1318 }
1319 .reviewers {
1319 .reviewers {
1320 width: 25%;
1320 width: 25%;
1321 min-width: 200px;
1321 min-width: 200px;
1322 }
1322 }
1323 .reviewers ul li {
1323 .reviewers ul li {
1324 position: relative;
1324 position: relative;
1325 width: 100%;
1325 width: 100%;
1326 padding-bottom: 8px;
1326 padding-bottom: 8px;
1327 list-style-type: none;
1327 list-style-type: none;
1328 }
1328 }
1329
1329
1330 .reviewer_entry {
1330 .reviewer_entry {
1331 min-height: 55px;
1331 min-height: 55px;
1332 }
1332 }
1333
1333
1334 .reviewers_member {
1334 .reviewers_member {
1335 width: 100%;
1335 width: 100%;
1336 overflow: auto;
1336 overflow: auto;
1337 }
1337 }
1338 .reviewer_reason {
1338 .reviewer_reason {
1339 padding-left: 20px;
1339 padding-left: 20px;
1340 line-height: 1.5em;
1340 line-height: 1.5em;
1341 }
1341 }
1342 .reviewer_status {
1342 .reviewer_status {
1343 display: inline-block;
1343 display: inline-block;
1344 vertical-align: top;
1344 vertical-align: top;
1345 width: 25px;
1345 width: 25px;
1346 min-width: 25px;
1346 min-width: 25px;
1347 height: 1.2em;
1347 height: 1.2em;
1348 margin-top: 3px;
1348 margin-top: 3px;
1349 line-height: 1em;
1349 line-height: 1em;
1350 }
1350 }
1351
1351
1352 .reviewer_name {
1352 .reviewer_name {
1353 display: inline-block;
1353 display: inline-block;
1354 max-width: 83%;
1354 max-width: 83%;
1355 padding-right: 20px;
1355 padding-right: 20px;
1356 vertical-align: middle;
1356 vertical-align: middle;
1357 line-height: 1;
1357 line-height: 1;
1358
1358
1359 .rc-user {
1359 .rc-user {
1360 min-width: 0;
1360 min-width: 0;
1361 margin: -2px 1em 0 0;
1361 margin: -2px 1em 0 0;
1362 }
1362 }
1363
1363
1364 .reviewer {
1364 .reviewer {
1365 float: left;
1365 float: left;
1366 }
1366 }
1367 }
1367 }
1368
1368
1369 .reviewer_member_mandatory {
1369 .reviewer_member_mandatory {
1370 position: absolute;
1370 position: absolute;
1371 left: 15px;
1371 left: 15px;
1372 top: 8px;
1372 top: 8px;
1373 width: 16px;
1373 width: 16px;
1374 font-size: 11px;
1374 font-size: 11px;
1375 margin: 0;
1375 margin: 0;
1376 padding: 0;
1376 padding: 0;
1377 color: black;
1377 color: black;
1378 }
1378 }
1379
1379
1380 .reviewer_member_mandatory_remove,
1380 .reviewer_member_mandatory_remove,
1381 .reviewer_member_remove {
1381 .reviewer_member_remove {
1382 position: absolute;
1382 position: absolute;
1383 right: 0;
1383 right: 0;
1384 top: 0;
1384 top: 0;
1385 width: 16px;
1385 width: 16px;
1386 margin-bottom: 10px;
1386 margin-bottom: 10px;
1387 padding: 0;
1387 padding: 0;
1388 color: black;
1388 color: black;
1389 }
1389 }
1390
1390
1391 .reviewer_member_mandatory_remove {
1391 .reviewer_member_mandatory_remove {
1392 color: @grey4;
1392 color: @grey4;
1393 }
1393 }
1394
1394
1395 .reviewer_member_status {
1395 .reviewer_member_status {
1396 margin-top: 5px;
1396 margin-top: 5px;
1397 }
1397 }
1398 .pr-summary #summary{
1398 .pr-summary #summary{
1399 width: 100%;
1399 width: 100%;
1400 }
1400 }
1401 .pr-summary .action_button:hover {
1401 .pr-summary .action_button:hover {
1402 border: 0;
1402 border: 0;
1403 cursor: pointer;
1403 cursor: pointer;
1404 }
1404 }
1405 .pr-details-title {
1405 .pr-details-title {
1406 padding-bottom: 8px;
1406 padding-bottom: 8px;
1407 border-bottom: @border-thickness solid @grey5;
1407 border-bottom: @border-thickness solid @grey5;
1408
1408
1409 .action_button.disabled {
1409 .action_button.disabled {
1410 color: @grey4;
1410 color: @grey4;
1411 cursor: inherit;
1411 cursor: inherit;
1412 }
1412 }
1413 .action_button {
1413 .action_button {
1414 color: @rcblue;
1414 color: @rcblue;
1415 }
1415 }
1416 }
1416 }
1417 .pr-details-content {
1417 .pr-details-content {
1418 margin-top: @textmargin;
1418 margin-top: @textmargin;
1419 margin-bottom: @textmargin;
1419 margin-bottom: @textmargin;
1420 }
1420 }
1421
1421
1422 .pr-reviewer-rules {
1422 .pr-reviewer-rules {
1423 padding: 10px 0px 20px 0px;
1423 padding: 10px 0px 20px 0px;
1424 }
1424 }
1425
1425
1426 .group_members {
1426 .group_members {
1427 margin-top: 0;
1427 margin-top: 0;
1428 padding: 0;
1428 padding: 0;
1429 list-style: outside none none;
1429 list-style: outside none none;
1430
1430
1431 img {
1431 img {
1432 height: @gravatar-size;
1432 height: @gravatar-size;
1433 width: @gravatar-size;
1433 width: @gravatar-size;
1434 margin-right: .5em;
1434 margin-right: .5em;
1435 margin-left: 3px;
1435 margin-left: 3px;
1436 }
1436 }
1437
1437
1438 .to-delete {
1438 .to-delete {
1439 .user {
1439 .user {
1440 text-decoration: line-through;
1440 text-decoration: line-through;
1441 }
1441 }
1442 }
1442 }
1443 }
1443 }
1444
1444
1445 .compare_view_commits_title {
1445 .compare_view_commits_title {
1446 .disabled {
1446 .disabled {
1447 cursor: inherit;
1447 cursor: inherit;
1448 &:hover{
1448 &:hover{
1449 background-color: inherit;
1449 background-color: inherit;
1450 color: inherit;
1450 color: inherit;
1451 }
1451 }
1452 }
1452 }
1453 }
1453 }
1454
1454
1455 .subtitle-compare {
1455 .subtitle-compare {
1456 margin: -15px 0px 0px 0px;
1456 margin: -15px 0px 0px 0px;
1457 }
1457 }
1458
1458
1459 .comments-summary-td {
1459 .comments-summary-td {
1460 border-top: 1px dashed @grey5;
1460 border-top: 1px dashed @grey5;
1461 }
1461 }
1462
1462
1463 // new entry in group_members
1463 // new entry in group_members
1464 .td-author-new-entry {
1464 .td-author-new-entry {
1465 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1465 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1466 }
1466 }
1467
1467
1468 .usergroup_member_remove {
1468 .usergroup_member_remove {
1469 width: 16px;
1469 width: 16px;
1470 margin-bottom: 10px;
1470 margin-bottom: 10px;
1471 padding: 0;
1471 padding: 0;
1472 color: black !important;
1472 color: black !important;
1473 cursor: pointer;
1473 cursor: pointer;
1474 }
1474 }
1475
1475
1476 .reviewer_ac .ac-input {
1476 .reviewer_ac .ac-input {
1477 width: 92%;
1477 width: 92%;
1478 margin-bottom: 1em;
1478 margin-bottom: 1em;
1479 }
1479 }
1480
1480
1481 .compare_view_commits tr{
1481 .compare_view_commits tr{
1482 height: 20px;
1482 height: 20px;
1483 }
1483 }
1484 .compare_view_commits td {
1484 .compare_view_commits td {
1485 vertical-align: top;
1485 vertical-align: top;
1486 padding-top: 10px;
1486 padding-top: 10px;
1487 }
1487 }
1488 .compare_view_commits .author {
1488 .compare_view_commits .author {
1489 margin-left: 5px;
1489 margin-left: 5px;
1490 }
1490 }
1491
1491
1492 .compare_view_commits {
1492 .compare_view_commits {
1493 .color-a {
1493 .color-a {
1494 color: @alert1;
1494 color: @alert1;
1495 }
1495 }
1496
1496
1497 .color-c {
1497 .color-c {
1498 color: @color3;
1498 color: @color3;
1499 }
1499 }
1500
1500
1501 .color-r {
1501 .color-r {
1502 color: @color5;
1502 color: @color5;
1503 }
1503 }
1504
1504
1505 .color-a-bg {
1505 .color-a-bg {
1506 background-color: @alert1;
1506 background-color: @alert1;
1507 }
1507 }
1508
1508
1509 .color-c-bg {
1509 .color-c-bg {
1510 background-color: @alert3;
1510 background-color: @alert3;
1511 }
1511 }
1512
1512
1513 .color-r-bg {
1513 .color-r-bg {
1514 background-color: @alert2;
1514 background-color: @alert2;
1515 }
1515 }
1516
1516
1517 .color-a-border {
1517 .color-a-border {
1518 border: 1px solid @alert1;
1518 border: 1px solid @alert1;
1519 }
1519 }
1520
1520
1521 .color-c-border {
1521 .color-c-border {
1522 border: 1px solid @alert3;
1522 border: 1px solid @alert3;
1523 }
1523 }
1524
1524
1525 .color-r-border {
1525 .color-r-border {
1526 border: 1px solid @alert2;
1526 border: 1px solid @alert2;
1527 }
1527 }
1528
1528
1529 .commit-change-indicator {
1529 .commit-change-indicator {
1530 width: 15px;
1530 width: 15px;
1531 height: 15px;
1531 height: 15px;
1532 position: relative;
1532 position: relative;
1533 left: 15px;
1533 left: 15px;
1534 }
1534 }
1535
1535
1536 .commit-change-content {
1536 .commit-change-content {
1537 text-align: center;
1537 text-align: center;
1538 vertical-align: middle;
1538 vertical-align: middle;
1539 line-height: 15px;
1539 line-height: 15px;
1540 }
1540 }
1541 }
1541 }
1542
1542
1543 .compare_view_filepath {
1543 .compare_view_filepath {
1544 color: @grey1;
1544 color: @grey1;
1545 }
1545 }
1546
1546
1547 .show_more {
1547 .show_more {
1548 display: inline-block;
1548 display: inline-block;
1549 width: 0;
1549 width: 0;
1550 height: 0;
1550 height: 0;
1551 vertical-align: middle;
1551 vertical-align: middle;
1552 content: "";
1552 content: "";
1553 border: 4px solid;
1553 border: 4px solid;
1554 border-right-color: transparent;
1554 border-right-color: transparent;
1555 border-bottom-color: transparent;
1555 border-bottom-color: transparent;
1556 border-left-color: transparent;
1556 border-left-color: transparent;
1557 font-size: 0;
1557 font-size: 0;
1558 }
1558 }
1559
1559
1560 .journal_more .show_more {
1560 .journal_more .show_more {
1561 display: inline;
1561 display: inline;
1562
1562
1563 &:after {
1563 &:after {
1564 content: none;
1564 content: none;
1565 }
1565 }
1566 }
1566 }
1567
1567
1568 .compare_view_commits .collapse_commit:after {
1568 .compare_view_commits .collapse_commit:after {
1569 cursor: pointer;
1569 cursor: pointer;
1570 content: "\00A0\25B4";
1570 content: "\00A0\25B4";
1571 margin-left: -3px;
1571 margin-left: -3px;
1572 font-size: 17px;
1572 font-size: 17px;
1573 color: @grey4;
1573 color: @grey4;
1574 }
1574 }
1575
1575
1576 .diff_links {
1576 .diff_links {
1577 margin-left: 8px;
1577 margin-left: 8px;
1578 }
1578 }
1579
1579
1580 div.ancestor {
1580 div.ancestor {
1581 margin: -30px 0px;
1581 margin: -30px 0px;
1582 }
1582 }
1583
1583
1584 .cs_icon_td input[type="checkbox"] {
1584 .cs_icon_td input[type="checkbox"] {
1585 display: none;
1585 display: none;
1586 }
1586 }
1587
1587
1588 .cs_icon_td .expand_file_icon:after {
1588 .cs_icon_td .expand_file_icon:after {
1589 cursor: pointer;
1589 cursor: pointer;
1590 content: "\00A0\25B6";
1590 content: "\00A0\25B6";
1591 font-size: 12px;
1591 font-size: 12px;
1592 color: @grey4;
1592 color: @grey4;
1593 }
1593 }
1594
1594
1595 .cs_icon_td .collapse_file_icon:after {
1595 .cs_icon_td .collapse_file_icon:after {
1596 cursor: pointer;
1596 cursor: pointer;
1597 content: "\00A0\25BC";
1597 content: "\00A0\25BC";
1598 font-size: 12px;
1598 font-size: 12px;
1599 color: @grey4;
1599 color: @grey4;
1600 }
1600 }
1601
1601
1602 /*new binary
1602 /*new binary
1603 NEW_FILENODE = 1
1603 NEW_FILENODE = 1
1604 DEL_FILENODE = 2
1604 DEL_FILENODE = 2
1605 MOD_FILENODE = 3
1605 MOD_FILENODE = 3
1606 RENAMED_FILENODE = 4
1606 RENAMED_FILENODE = 4
1607 COPIED_FILENODE = 5
1607 COPIED_FILENODE = 5
1608 CHMOD_FILENODE = 6
1608 CHMOD_FILENODE = 6
1609 BIN_FILENODE = 7
1609 BIN_FILENODE = 7
1610 */
1610 */
1611 .cs_files_expand {
1611 .cs_files_expand {
1612 font-size: @basefontsize + 5px;
1612 font-size: @basefontsize + 5px;
1613 line-height: 1.8em;
1613 line-height: 1.8em;
1614 float: right;
1614 float: right;
1615 }
1615 }
1616
1616
1617 .cs_files_expand span{
1617 .cs_files_expand span{
1618 color: @rcblue;
1618 color: @rcblue;
1619 cursor: pointer;
1619 cursor: pointer;
1620 }
1620 }
1621 .cs_files {
1621 .cs_files {
1622 clear: both;
1622 clear: both;
1623 padding-bottom: @padding;
1623 padding-bottom: @padding;
1624
1624
1625 .cur_cs {
1625 .cur_cs {
1626 margin: 10px 2px;
1626 margin: 10px 2px;
1627 font-weight: bold;
1627 font-weight: bold;
1628 }
1628 }
1629
1629
1630 .node {
1630 .node {
1631 float: left;
1631 float: left;
1632 }
1632 }
1633
1633
1634 .changes {
1634 .changes {
1635 float: right;
1635 float: right;
1636 color: white;
1636 color: white;
1637 font-size: @basefontsize - 4px;
1637 font-size: @basefontsize - 4px;
1638 margin-top: 4px;
1638 margin-top: 4px;
1639 opacity: 0.6;
1639 opacity: 0.6;
1640 filter: Alpha(opacity=60); /* IE8 and earlier */
1640 filter: Alpha(opacity=60); /* IE8 and earlier */
1641
1641
1642 .added {
1642 .added {
1643 background-color: @alert1;
1643 background-color: @alert1;
1644 float: left;
1644 float: left;
1645 text-align: center;
1645 text-align: center;
1646 }
1646 }
1647
1647
1648 .deleted {
1648 .deleted {
1649 background-color: @alert2;
1649 background-color: @alert2;
1650 float: left;
1650 float: left;
1651 text-align: center;
1651 text-align: center;
1652 }
1652 }
1653
1653
1654 .bin {
1654 .bin {
1655 background-color: @alert1;
1655 background-color: @alert1;
1656 text-align: center;
1656 text-align: center;
1657 }
1657 }
1658
1658
1659 /*new binary*/
1659 /*new binary*/
1660 .bin.bin1 {
1660 .bin.bin1 {
1661 background-color: @alert1;
1661 background-color: @alert1;
1662 text-align: center;
1662 text-align: center;
1663 }
1663 }
1664
1664
1665 /*deleted binary*/
1665 /*deleted binary*/
1666 .bin.bin2 {
1666 .bin.bin2 {
1667 background-color: @alert2;
1667 background-color: @alert2;
1668 text-align: center;
1668 text-align: center;
1669 }
1669 }
1670
1670
1671 /*mod binary*/
1671 /*mod binary*/
1672 .bin.bin3 {
1672 .bin.bin3 {
1673 background-color: @grey2;
1673 background-color: @grey2;
1674 text-align: center;
1674 text-align: center;
1675 }
1675 }
1676
1676
1677 /*rename file*/
1677 /*rename file*/
1678 .bin.bin4 {
1678 .bin.bin4 {
1679 background-color: @alert4;
1679 background-color: @alert4;
1680 text-align: center;
1680 text-align: center;
1681 }
1681 }
1682
1682
1683 /*copied file*/
1683 /*copied file*/
1684 .bin.bin5 {
1684 .bin.bin5 {
1685 background-color: @alert4;
1685 background-color: @alert4;
1686 text-align: center;
1686 text-align: center;
1687 }
1687 }
1688
1688
1689 /*chmod file*/
1689 /*chmod file*/
1690 .bin.bin6 {
1690 .bin.bin6 {
1691 background-color: @grey2;
1691 background-color: @grey2;
1692 text-align: center;
1692 text-align: center;
1693 }
1693 }
1694 }
1694 }
1695 }
1695 }
1696
1696
1697 .cs_files .cs_added, .cs_files .cs_A,
1697 .cs_files .cs_added, .cs_files .cs_A,
1698 .cs_files .cs_added, .cs_files .cs_M,
1698 .cs_files .cs_added, .cs_files .cs_M,
1699 .cs_files .cs_added, .cs_files .cs_D {
1699 .cs_files .cs_added, .cs_files .cs_D {
1700 height: 16px;
1700 height: 16px;
1701 padding-right: 10px;
1701 padding-right: 10px;
1702 margin-top: 7px;
1702 margin-top: 7px;
1703 text-align: left;
1703 text-align: left;
1704 }
1704 }
1705
1705
1706 .cs_icon_td {
1706 .cs_icon_td {
1707 min-width: 16px;
1707 min-width: 16px;
1708 width: 16px;
1708 width: 16px;
1709 }
1709 }
1710
1710
1711 .pull-request-merge {
1711 .pull-request-merge {
1712 border: 1px solid @grey5;
1712 border: 1px solid @grey5;
1713 padding: 10px 0px 20px;
1713 padding: 10px 0px 20px;
1714 margin-top: 10px;
1714 margin-top: 10px;
1715 margin-bottom: 20px;
1715 margin-bottom: 20px;
1716 }
1716 }
1717
1717
1718 .pull-request-merge ul {
1718 .pull-request-merge ul {
1719 padding: 0px 0px;
1719 padding: 0px 0px;
1720 }
1720 }
1721
1721
1722 .pull-request-merge li {
1722 .pull-request-merge li {
1723 list-style-type: none;
1723 list-style-type: none;
1724 }
1724 }
1725
1725
1726 .pull-request-merge .pull-request-wrap {
1726 .pull-request-merge .pull-request-wrap {
1727 height: auto;
1727 height: auto;
1728 padding: 0px 0px;
1728 padding: 0px 0px;
1729 text-align: right;
1729 text-align: right;
1730 }
1730 }
1731
1731
1732 .pull-request-merge span {
1732 .pull-request-merge span {
1733 margin-right: 5px;
1733 margin-right: 5px;
1734 }
1734 }
1735
1735
1736 .pull-request-merge-actions {
1736 .pull-request-merge-actions {
1737 min-height: 30px;
1737 min-height: 30px;
1738 padding: 0px 0px;
1738 padding: 0px 0px;
1739 }
1739 }
1740
1740
1741 .pull-request-merge-info {
1741 .pull-request-merge-info {
1742 padding: 0px 5px 5px 0px;
1742 padding: 0px 5px 5px 0px;
1743 }
1743 }
1744
1744
1745 .merge-status {
1745 .merge-status {
1746 margin-right: 5px;
1746 margin-right: 5px;
1747 }
1747 }
1748
1748
1749 .merge-message {
1749 .merge-message {
1750 font-size: 1.2em
1750 font-size: 1.2em
1751 }
1751 }
1752
1752
1753 .merge-message.success i,
1753 .merge-message.success i,
1754 .merge-icon.success i {
1754 .merge-icon.success i {
1755 color:@alert1;
1755 color:@alert1;
1756 }
1756 }
1757
1757
1758 .merge-message.warning i,
1758 .merge-message.warning i,
1759 .merge-icon.warning i {
1759 .merge-icon.warning i {
1760 color: @alert3;
1760 color: @alert3;
1761 }
1761 }
1762
1762
1763 .merge-message.error i,
1763 .merge-message.error i,
1764 .merge-icon.error i {
1764 .merge-icon.error i {
1765 color:@alert2;
1765 color:@alert2;
1766 }
1766 }
1767
1767
1768 .pr-versions {
1768 .pr-versions {
1769 font-size: 1.1em;
1769 font-size: 1.1em;
1770
1770
1771 table {
1771 table {
1772 padding: 0px 5px;
1772 padding: 0px 5px;
1773 }
1773 }
1774
1774
1775 td {
1775 td {
1776 line-height: 15px;
1776 line-height: 15px;
1777 }
1777 }
1778
1778
1779 .flag_status {
1779 .flag_status {
1780 margin: 0;
1780 margin: 0;
1781 }
1781 }
1782
1782
1783 .compare-radio-button {
1783 .compare-radio-button {
1784 position: relative;
1784 position: relative;
1785 top: -3px;
1785 top: -3px;
1786 }
1786 }
1787 }
1787 }
1788
1788
1789
1789
1790 #close_pull_request {
1790 #close_pull_request {
1791 margin-right: 0px;
1791 margin-right: 0px;
1792 }
1792 }
1793
1793
1794 .empty_data {
1794 .empty_data {
1795 color: @grey4;
1795 color: @grey4;
1796 }
1796 }
1797
1797
1798 #changeset_compare_view_content {
1798 #changeset_compare_view_content {
1799 margin-bottom: @space;
1799 margin-bottom: @space;
1800 clear: both;
1800 clear: both;
1801 width: 100%;
1801 width: 100%;
1802 box-sizing: border-box;
1802 box-sizing: border-box;
1803 .border-radius(@border-radius);
1803 .border-radius(@border-radius);
1804
1804
1805 .help-block {
1805 .help-block {
1806 margin: @padding 0;
1806 margin: @padding 0;
1807 color: @text-color;
1807 color: @text-color;
1808 &.pre-formatting {
1808 &.pre-formatting {
1809 white-space: pre;
1809 white-space: pre;
1810 }
1810 }
1811 }
1811 }
1812
1812
1813 .empty_data {
1813 .empty_data {
1814 margin: @padding 0;
1814 margin: @padding 0;
1815 }
1815 }
1816
1816
1817 .alert {
1817 .alert {
1818 margin-bottom: @space;
1818 margin-bottom: @space;
1819 }
1819 }
1820 }
1820 }
1821
1821
1822 .table_disp {
1822 .table_disp {
1823 .status {
1823 .status {
1824 width: auto;
1824 width: auto;
1825
1825
1826 .flag_status {
1826 .flag_status {
1827 float: left;
1827 float: left;
1828 }
1828 }
1829 }
1829 }
1830 }
1830 }
1831
1831
1832
1832
1833 .creation_in_progress {
1833 .creation_in_progress {
1834 color: @grey4
1834 color: @grey4
1835 }
1835 }
1836
1836
1837 .status_box_menu {
1837 .status_box_menu {
1838 margin: 0;
1838 margin: 0;
1839 }
1839 }
1840
1840
1841 .notification-table{
1841 .notification-table{
1842 margin-bottom: @space;
1842 margin-bottom: @space;
1843 display: table;
1843 display: table;
1844 width: 100%;
1844 width: 100%;
1845
1845
1846 .container{
1846 .container{
1847 display: table-row;
1847 display: table-row;
1848
1848
1849 .notification-header{
1849 .notification-header{
1850 border-bottom: @border-thickness solid @border-default-color;
1850 border-bottom: @border-thickness solid @border-default-color;
1851 }
1851 }
1852
1852
1853 .notification-subject{
1853 .notification-subject{
1854 display: table-cell;
1854 display: table-cell;
1855 }
1855 }
1856 }
1856 }
1857 }
1857 }
1858
1858
1859 // Notifications
1859 // Notifications
1860 .notification-header{
1860 .notification-header{
1861 display: table;
1861 display: table;
1862 width: 100%;
1862 width: 100%;
1863 padding: floor(@basefontsize/2) 0;
1863 padding: floor(@basefontsize/2) 0;
1864 line-height: 1em;
1864 line-height: 1em;
1865
1865
1866 .desc, .delete-notifications, .read-notifications{
1866 .desc, .delete-notifications, .read-notifications{
1867 display: table-cell;
1867 display: table-cell;
1868 text-align: left;
1868 text-align: left;
1869 }
1869 }
1870
1870
1871 .desc{
1871 .desc{
1872 width: 1163px;
1872 width: 1163px;
1873 }
1873 }
1874
1874
1875 .delete-notifications, .read-notifications{
1875 .delete-notifications, .read-notifications{
1876 width: 35px;
1876 width: 35px;
1877 min-width: 35px; //fixes when only one button is displayed
1877 min-width: 35px; //fixes when only one button is displayed
1878 }
1878 }
1879 }
1879 }
1880
1880
1881 .notification-body {
1881 .notification-body {
1882 .markdown-block,
1882 .markdown-block,
1883 .rst-block {
1883 .rst-block {
1884 padding: @padding 0;
1884 padding: @padding 0;
1885 }
1885 }
1886
1886
1887 .notification-subject {
1887 .notification-subject {
1888 padding: @textmargin 0;
1888 padding: @textmargin 0;
1889 border-bottom: @border-thickness solid @border-default-color;
1889 border-bottom: @border-thickness solid @border-default-color;
1890 }
1890 }
1891 }
1891 }
1892
1892
1893
1893
1894 .notifications_buttons{
1894 .notifications_buttons{
1895 float: right;
1895 float: right;
1896 }
1896 }
1897
1897
1898 #notification-status{
1898 #notification-status{
1899 display: inline;
1899 display: inline;
1900 }
1900 }
1901
1901
1902 // Repositories
1902 // Repositories
1903
1903
1904 #summary.fields{
1904 #summary.fields{
1905 display: table;
1905 display: table;
1906
1906
1907 .field{
1907 .field{
1908 display: table-row;
1908 display: table-row;
1909
1909
1910 .label-summary{
1910 .label-summary{
1911 display: table-cell;
1911 display: table-cell;
1912 min-width: @label-summary-minwidth;
1912 min-width: @label-summary-minwidth;
1913 padding-top: @padding/2;
1913 padding-top: @padding/2;
1914 padding-bottom: @padding/2;
1914 padding-bottom: @padding/2;
1915 padding-right: @padding/2;
1915 padding-right: @padding/2;
1916 }
1916 }
1917
1917
1918 .input{
1918 .input{
1919 display: table-cell;
1919 display: table-cell;
1920 padding: @padding/2;
1920 padding: @padding/2;
1921
1921
1922 input{
1922 input{
1923 min-width: 29em;
1923 min-width: 29em;
1924 padding: @padding/4;
1924 padding: @padding/4;
1925 }
1925 }
1926 }
1926 }
1927 .statistics, .downloads{
1927 .statistics, .downloads{
1928 .disabled{
1928 .disabled{
1929 color: @grey4;
1929 color: @grey4;
1930 }
1930 }
1931 }
1931 }
1932 }
1932 }
1933 }
1933 }
1934
1934
1935 #summary{
1935 #summary{
1936 width: 70%;
1936 width: 70%;
1937 }
1937 }
1938
1938
1939
1939
1940 // Journal
1940 // Journal
1941 .journal.title {
1941 .journal.title {
1942 h5 {
1942 h5 {
1943 float: left;
1943 float: left;
1944 margin: 0;
1944 margin: 0;
1945 width: 70%;
1945 width: 70%;
1946 }
1946 }
1947
1947
1948 ul {
1948 ul {
1949 float: right;
1949 float: right;
1950 display: inline-block;
1950 display: inline-block;
1951 margin: 0;
1951 margin: 0;
1952 width: 30%;
1952 width: 30%;
1953 text-align: right;
1953 text-align: right;
1954
1954
1955 li {
1955 li {
1956 display: inline;
1956 display: inline;
1957 font-size: @journal-fontsize;
1957 font-size: @journal-fontsize;
1958 line-height: 1em;
1958 line-height: 1em;
1959
1959
1960 list-style-type: none;
1960 list-style-type: none;
1961 }
1961 }
1962 }
1962 }
1963 }
1963 }
1964
1964
1965 .filterexample {
1965 .filterexample {
1966 position: absolute;
1966 position: absolute;
1967 top: 95px;
1967 top: 95px;
1968 left: @contentpadding;
1968 left: @contentpadding;
1969 color: @rcblue;
1969 color: @rcblue;
1970 font-size: 11px;
1970 font-size: 11px;
1971 font-family: @text-regular;
1971 font-family: @text-regular;
1972 cursor: help;
1972 cursor: help;
1973
1973
1974 &:hover {
1974 &:hover {
1975 color: @rcdarkblue;
1975 color: @rcdarkblue;
1976 }
1976 }
1977
1977
1978 @media (max-width:768px) {
1978 @media (max-width:768px) {
1979 position: relative;
1979 position: relative;
1980 top: auto;
1980 top: auto;
1981 left: auto;
1981 left: auto;
1982 display: block;
1982 display: block;
1983 }
1983 }
1984 }
1984 }
1985
1985
1986
1986
1987 #journal{
1987 #journal{
1988 margin-bottom: @space;
1988 margin-bottom: @space;
1989
1989
1990 .journal_day{
1990 .journal_day{
1991 margin-bottom: @textmargin/2;
1991 margin-bottom: @textmargin/2;
1992 padding-bottom: @textmargin/2;
1992 padding-bottom: @textmargin/2;
1993 font-size: @journal-fontsize;
1993 font-size: @journal-fontsize;
1994 border-bottom: @border-thickness solid @border-default-color;
1994 border-bottom: @border-thickness solid @border-default-color;
1995 }
1995 }
1996
1996
1997 .journal_container{
1997 .journal_container{
1998 margin-bottom: @space;
1998 margin-bottom: @space;
1999
1999
2000 .journal_user{
2000 .journal_user{
2001 display: inline-block;
2001 display: inline-block;
2002 }
2002 }
2003 .journal_action_container{
2003 .journal_action_container{
2004 display: block;
2004 display: block;
2005 margin-top: @textmargin;
2005 margin-top: @textmargin;
2006
2006
2007 div{
2007 div{
2008 display: inline;
2008 display: inline;
2009 }
2009 }
2010
2010
2011 div.journal_action_params{
2011 div.journal_action_params{
2012 display: block;
2012 display: block;
2013 }
2013 }
2014
2014
2015 div.journal_repo:after{
2015 div.journal_repo:after{
2016 content: "\A";
2016 content: "\A";
2017 white-space: pre;
2017 white-space: pre;
2018 }
2018 }
2019
2019
2020 div.date{
2020 div.date{
2021 display: block;
2021 display: block;
2022 margin-bottom: @textmargin;
2022 margin-bottom: @textmargin;
2023 }
2023 }
2024 }
2024 }
2025 }
2025 }
2026 }
2026 }
2027
2027
2028 // Files
2028 // Files
2029 .edit-file-title {
2029 .edit-file-title {
2030 border-bottom: @border-thickness solid @border-default-color;
2030 border-bottom: @border-thickness solid @border-default-color;
2031
2031
2032 .breadcrumbs {
2032 .breadcrumbs {
2033 margin-bottom: 0;
2033 margin-bottom: 0;
2034 }
2034 }
2035 }
2035 }
2036
2036
2037 .edit-file-fieldset {
2037 .edit-file-fieldset {
2038 margin-top: @sidebarpadding;
2038 margin-top: @sidebarpadding;
2039
2039
2040 .fieldset {
2040 .fieldset {
2041 .left-label {
2041 .left-label {
2042 width: 13%;
2042 width: 13%;
2043 }
2043 }
2044 .right-content {
2044 .right-content {
2045 width: 87%;
2045 width: 87%;
2046 max-width: 100%;
2046 max-width: 100%;
2047 }
2047 }
2048 .filename-label {
2048 .filename-label {
2049 margin-top: 13px;
2049 margin-top: 13px;
2050 }
2050 }
2051 .commit-message-label {
2051 .commit-message-label {
2052 margin-top: 4px;
2052 margin-top: 4px;
2053 }
2053 }
2054 .file-upload-input {
2054 .file-upload-input {
2055 input {
2055 input {
2056 display: none;
2056 display: none;
2057 }
2057 }
2058 margin-top: 10px;
2058 margin-top: 10px;
2059 }
2059 }
2060 .file-upload-label {
2060 .file-upload-label {
2061 margin-top: 10px;
2061 margin-top: 10px;
2062 }
2062 }
2063 p {
2063 p {
2064 margin-top: 5px;
2064 margin-top: 5px;
2065 }
2065 }
2066
2066
2067 }
2067 }
2068 .custom-path-link {
2068 .custom-path-link {
2069 margin-left: 5px;
2069 margin-left: 5px;
2070 }
2070 }
2071 #commit {
2071 #commit {
2072 resize: vertical;
2072 resize: vertical;
2073 }
2073 }
2074 }
2074 }
2075
2075
2076 .delete-file-preview {
2076 .delete-file-preview {
2077 max-height: 250px;
2077 max-height: 250px;
2078 }
2078 }
2079
2079
2080 .new-file,
2080 .new-file,
2081 #filter_activate,
2081 #filter_activate,
2082 #filter_deactivate {
2082 #filter_deactivate {
2083 float: left;
2083 float: left;
2084 margin: 0 0 0 15px;
2084 margin: 0 0 0 15px;
2085 }
2085 }
2086
2086
2087 h3.files_location{
2087 h3.files_location{
2088 line-height: 2.4em;
2088 line-height: 2.4em;
2089 }
2089 }
2090
2090
2091 .browser-nav {
2091 .browser-nav {
2092 display: table;
2092 display: table;
2093 margin-bottom: @space;
2093 margin-bottom: @space;
2094
2094
2095
2095
2096 .info_box {
2096 .info_box {
2097 display: inline-table;
2097 display: inline-table;
2098 height: 2.5em;
2098 height: 2.5em;
2099
2099
2100 .browser-cur-rev, .info_box_elem {
2100 .browser-cur-rev, .info_box_elem {
2101 display: table-cell;
2101 display: table-cell;
2102 vertical-align: middle;
2102 vertical-align: middle;
2103 }
2103 }
2104
2104
2105 .info_box_elem {
2105 .info_box_elem {
2106 border-top: @border-thickness solid @rcblue;
2106 border-top: @border-thickness solid @rcblue;
2107 border-bottom: @border-thickness solid @rcblue;
2107 border-bottom: @border-thickness solid @rcblue;
2108
2108
2109 #at_rev, a {
2109 #at_rev, a {
2110 padding: 0.6em 0.9em;
2110 padding: 0.6em 0.9em;
2111 margin: 0;
2111 margin: 0;
2112 .box-shadow(none);
2112 .box-shadow(none);
2113 border: 0;
2113 border: 0;
2114 height: 12px;
2114 height: 12px;
2115 }
2115 }
2116
2116
2117 input#at_rev {
2117 input#at_rev {
2118 max-width: 50px;
2118 max-width: 50px;
2119 text-align: right;
2119 text-align: right;
2120 }
2120 }
2121
2121
2122 &.previous {
2122 &.previous {
2123 border: @border-thickness solid @rcblue;
2123 border: @border-thickness solid @rcblue;
2124 .disabled {
2124 .disabled {
2125 color: @grey4;
2125 color: @grey4;
2126 cursor: not-allowed;
2126 cursor: not-allowed;
2127 }
2127 }
2128 }
2128 }
2129
2129
2130 &.next {
2130 &.next {
2131 border: @border-thickness solid @rcblue;
2131 border: @border-thickness solid @rcblue;
2132 .disabled {
2132 .disabled {
2133 color: @grey4;
2133 color: @grey4;
2134 cursor: not-allowed;
2134 cursor: not-allowed;
2135 }
2135 }
2136 }
2136 }
2137 }
2137 }
2138
2138
2139 .browser-cur-rev {
2139 .browser-cur-rev {
2140
2140
2141 span{
2141 span{
2142 margin: 0;
2142 margin: 0;
2143 color: @rcblue;
2143 color: @rcblue;
2144 height: 12px;
2144 height: 12px;
2145 display: inline-block;
2145 display: inline-block;
2146 padding: 0.7em 1em ;
2146 padding: 0.7em 1em ;
2147 border: @border-thickness solid @rcblue;
2147 border: @border-thickness solid @rcblue;
2148 margin-right: @padding;
2148 margin-right: @padding;
2149 }
2149 }
2150 }
2150 }
2151 }
2151 }
2152
2152
2153 .search_activate {
2153 .search_activate {
2154 display: table-cell;
2154 display: table-cell;
2155 vertical-align: middle;
2155 vertical-align: middle;
2156
2156
2157 input, label{
2157 input, label{
2158 margin: 0;
2158 margin: 0;
2159 padding: 0;
2159 padding: 0;
2160 }
2160 }
2161
2161
2162 input{
2162 input{
2163 margin-left: @textmargin;
2163 margin-left: @textmargin;
2164 }
2164 }
2165
2165
2166 }
2166 }
2167 }
2167 }
2168
2168
2169 .browser-cur-rev{
2169 .browser-cur-rev{
2170 margin-bottom: @textmargin;
2170 margin-bottom: @textmargin;
2171 }
2171 }
2172
2172
2173 #node_filter_box_loading{
2173 #node_filter_box_loading{
2174 .info_text;
2174 .info_text;
2175 }
2175 }
2176
2176
2177 .browser-search {
2177 .browser-search {
2178 margin: -25px 0px 5px 0px;
2178 margin: -25px 0px 5px 0px;
2179 }
2179 }
2180
2180
2181 .node-filter {
2181 .node-filter {
2182 font-size: @repo-title-fontsize;
2182 font-size: @repo-title-fontsize;
2183 padding: 4px 0px 0px 0px;
2183 padding: 4px 0px 0px 0px;
2184
2184
2185 .node-filter-path {
2185 .node-filter-path {
2186 float: left;
2186 float: left;
2187 color: @grey4;
2187 color: @grey4;
2188 }
2188 }
2189 .node-filter-input {
2189 .node-filter-input {
2190 float: left;
2190 float: left;
2191 margin: -2px 0px 0px 2px;
2191 margin: -2px 0px 0px 2px;
2192 input {
2192 input {
2193 padding: 2px;
2193 padding: 2px;
2194 border: none;
2194 border: none;
2195 font-size: @repo-title-fontsize;
2195 font-size: @repo-title-fontsize;
2196 }
2196 }
2197 }
2197 }
2198 }
2198 }
2199
2199
2200
2200
2201 .browser-result{
2201 .browser-result{
2202 td a{
2202 td a{
2203 margin-left: 0.5em;
2203 margin-left: 0.5em;
2204 display: inline-block;
2204 display: inline-block;
2205
2205
2206 em {
2206 em {
2207 font-weight: @text-bold-weight;
2207 font-weight: @text-bold-weight;
2208 font-family: @text-bold;
2208 font-family: @text-bold;
2209 }
2209 }
2210 }
2210 }
2211 }
2211 }
2212
2212
2213 .browser-highlight{
2213 .browser-highlight{
2214 background-color: @grey5-alpha;
2214 background-color: @grey5-alpha;
2215 }
2215 }
2216
2216
2217
2217
2218 // Search
2218 // Search
2219
2219
2220 .search-form{
2220 .search-form{
2221 #q {
2221 #q {
2222 width: @search-form-width;
2222 width: @search-form-width;
2223 }
2223 }
2224 .fields{
2224 .fields{
2225 margin: 0 0 @space;
2225 margin: 0 0 @space;
2226 }
2226 }
2227
2227
2228 label{
2228 label{
2229 display: inline-block;
2229 display: inline-block;
2230 margin-right: @textmargin;
2230 margin-right: @textmargin;
2231 padding-top: 0.25em;
2231 padding-top: 0.25em;
2232 }
2232 }
2233
2233
2234
2234
2235 .results{
2235 .results{
2236 clear: both;
2236 clear: both;
2237 margin: 0 0 @padding;
2237 margin: 0 0 @padding;
2238 }
2238 }
2239
2240 .search-tags {
2241 padding: 5px 0;
2242 }
2239 }
2243 }
2240
2244
2241 div.search-feedback-items {
2245 div.search-feedback-items {
2242 display: inline-block;
2246 display: inline-block;
2243 }
2247 }
2244
2248
2245 div.search-code-body {
2249 div.search-code-body {
2246 background-color: #ffffff; padding: 5px 0 5px 10px;
2250 background-color: #ffffff; padding: 5px 0 5px 10px;
2247 pre {
2251 pre {
2248 .match { background-color: #faffa6;}
2252 .match { background-color: #faffa6;}
2249 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2253 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2250 }
2254 }
2251 }
2255 }
2252
2256
2253 .expand_commit.search {
2257 .expand_commit.search {
2254 .show_more.open {
2258 .show_more.open {
2255 height: auto;
2259 height: auto;
2256 max-height: none;
2260 max-height: none;
2257 }
2261 }
2258 }
2262 }
2259
2263
2260 .search-results {
2264 .search-results {
2261
2265
2262 h2 {
2266 h2 {
2263 margin-bottom: 0;
2267 margin-bottom: 0;
2264 }
2268 }
2265 .codeblock {
2269 .codeblock {
2266 border: none;
2270 border: none;
2267 background: transparent;
2271 background: transparent;
2268 }
2272 }
2269
2273
2270 .codeblock-header {
2274 .codeblock-header {
2271 border: none;
2275 border: none;
2272 background: transparent;
2276 background: transparent;
2273 }
2277 }
2274
2278
2275 .code-body {
2279 .code-body {
2276 border: @border-thickness solid @border-default-color;
2280 border: @border-thickness solid @border-default-color;
2277 .border-radius(@border-radius);
2281 .border-radius(@border-radius);
2278 }
2282 }
2279
2283
2280 .td-commit {
2284 .td-commit {
2281 &:extend(pre);
2285 &:extend(pre);
2282 border-bottom: @border-thickness solid @border-default-color;
2286 border-bottom: @border-thickness solid @border-default-color;
2283 }
2287 }
2284
2288
2285 .message {
2289 .message {
2286 height: auto;
2290 height: auto;
2287 max-width: 350px;
2291 max-width: 350px;
2288 white-space: normal;
2292 white-space: normal;
2289 text-overflow: initial;
2293 text-overflow: initial;
2290 overflow: visible;
2294 overflow: visible;
2291
2295
2292 .match { background-color: #faffa6;}
2296 .match { background-color: #faffa6;}
2293 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2297 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2294 }
2298 }
2295
2299
2296 }
2300 }
2297
2301
2298 table.rctable td.td-search-results div {
2302 table.rctable td.td-search-results div {
2299 max-width: 100%;
2303 max-width: 100%;
2300 }
2304 }
2301
2305
2302 #tip-box, .tip-box{
2306 #tip-box, .tip-box{
2303 padding: @menupadding/2;
2307 padding: @menupadding/2;
2304 display: block;
2308 display: block;
2305 border: @border-thickness solid @border-highlight-color;
2309 border: @border-thickness solid @border-highlight-color;
2306 .border-radius(@border-radius);
2310 .border-radius(@border-radius);
2307 background-color: white;
2311 background-color: white;
2308 z-index: 99;
2312 z-index: 99;
2309 white-space: pre-wrap;
2313 white-space: pre-wrap;
2310 }
2314 }
2311
2315
2312 #linktt {
2316 #linktt {
2313 width: 79px;
2317 width: 79px;
2314 }
2318 }
2315
2319
2316 #help_kb .modal-content{
2320 #help_kb .modal-content{
2317 max-width: 750px;
2321 max-width: 750px;
2318 margin: 10% auto;
2322 margin: 10% auto;
2319
2323
2320 table{
2324 table{
2321 td,th{
2325 td,th{
2322 border-bottom: none;
2326 border-bottom: none;
2323 line-height: 2.5em;
2327 line-height: 2.5em;
2324 }
2328 }
2325 th{
2329 th{
2326 padding-bottom: @textmargin/2;
2330 padding-bottom: @textmargin/2;
2327 }
2331 }
2328 td.keys{
2332 td.keys{
2329 text-align: center;
2333 text-align: center;
2330 }
2334 }
2331 }
2335 }
2332
2336
2333 .block-left{
2337 .block-left{
2334 width: 45%;
2338 width: 45%;
2335 margin-right: 5%;
2339 margin-right: 5%;
2336 }
2340 }
2337 .modal-footer{
2341 .modal-footer{
2338 clear: both;
2342 clear: both;
2339 }
2343 }
2340 .key.tag{
2344 .key.tag{
2341 padding: 0.5em;
2345 padding: 0.5em;
2342 background-color: @rcblue;
2346 background-color: @rcblue;
2343 color: white;
2347 color: white;
2344 border-color: @rcblue;
2348 border-color: @rcblue;
2345 .box-shadow(none);
2349 .box-shadow(none);
2346 }
2350 }
2347 }
2351 }
2348
2352
2349
2353
2350
2354
2351 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2355 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2352
2356
2353 @import 'statistics-graph';
2357 @import 'statistics-graph';
2354 @import 'tables';
2358 @import 'tables';
2355 @import 'forms';
2359 @import 'forms';
2356 @import 'diff';
2360 @import 'diff';
2357 @import 'summary';
2361 @import 'summary';
2358 @import 'navigation';
2362 @import 'navigation';
2359
2363
2360 //--- SHOW/HIDE SECTIONS --//
2364 //--- SHOW/HIDE SECTIONS --//
2361
2365
2362 .btn-collapse {
2366 .btn-collapse {
2363 float: right;
2367 float: right;
2364 text-align: right;
2368 text-align: right;
2365 font-family: @text-light;
2369 font-family: @text-light;
2366 font-size: @basefontsize;
2370 font-size: @basefontsize;
2367 cursor: pointer;
2371 cursor: pointer;
2368 border: none;
2372 border: none;
2369 color: @rcblue;
2373 color: @rcblue;
2370 }
2374 }
2371
2375
2372 table.rctable,
2376 table.rctable,
2373 table.dataTable {
2377 table.dataTable {
2374 .btn-collapse {
2378 .btn-collapse {
2375 float: right;
2379 float: right;
2376 text-align: right;
2380 text-align: right;
2377 }
2381 }
2378 }
2382 }
2379
2383
2380 table.rctable {
2384 table.rctable {
2381 &.permissions {
2385 &.permissions {
2382
2386
2383 th.td-owner {
2387 th.td-owner {
2384 padding: 0;
2388 padding: 0;
2385 }
2389 }
2386
2390
2387 th {
2391 th {
2388 font-weight: normal;
2392 font-weight: normal;
2389 padding: 0 5px;
2393 padding: 0 5px;
2390 }
2394 }
2391
2395
2392 }
2396 }
2393 }
2397 }
2394
2398
2395
2399
2396 // TODO: johbo: Fix for IE10, this avoids that we see a border
2400 // TODO: johbo: Fix for IE10, this avoids that we see a border
2397 // and padding around checkboxes and radio boxes. Move to the right place,
2401 // and padding around checkboxes and radio boxes. Move to the right place,
2398 // or better: Remove this once we did the form refactoring.
2402 // or better: Remove this once we did the form refactoring.
2399 input[type=checkbox],
2403 input[type=checkbox],
2400 input[type=radio] {
2404 input[type=radio] {
2401 padding: 0;
2405 padding: 0;
2402 border: none;
2406 border: none;
2403 }
2407 }
2404
2408
2405 .toggle-ajax-spinner{
2409 .toggle-ajax-spinner{
2406 height: 16px;
2410 height: 16px;
2407 width: 16px;
2411 width: 16px;
2408 }
2412 }
2409
2413
2410
2414
2411 .markup-form .clearfix {
2415 .markup-form .clearfix {
2412 .border-radius(@border-radius);
2416 .border-radius(@border-radius);
2413 margin: 0px;
2417 margin: 0px;
2414 }
2418 }
2415
2419
2416 .markup-form-area {
2420 .markup-form-area {
2417 padding: 8px 12px;
2421 padding: 8px 12px;
2418 border: 1px solid @grey4;
2422 border: 1px solid @grey4;
2419 .border-radius(@border-radius);
2423 .border-radius(@border-radius);
2420 }
2424 }
2421
2425
2422 .markup-form-area-header .nav-links {
2426 .markup-form-area-header .nav-links {
2423 display: flex;
2427 display: flex;
2424 flex-flow: row wrap;
2428 flex-flow: row wrap;
2425 -webkit-flex-flow: row wrap;
2429 -webkit-flex-flow: row wrap;
2426 width: 100%;
2430 width: 100%;
2427 }
2431 }
2428
2432
2429 .markup-form-area-footer {
2433 .markup-form-area-footer {
2430 display: flex;
2434 display: flex;
2431 }
2435 }
2432
2436
2433 .markup-form-area-footer .toolbar {
2437 .markup-form-area-footer .toolbar {
2434
2438
2435 }
2439 }
2436
2440
2437 // markup Form
2441 // markup Form
2438 div.markup-form {
2442 div.markup-form {
2439 margin-top: 20px;
2443 margin-top: 20px;
2440 }
2444 }
2441
2445
2442 .markup-form strong {
2446 .markup-form strong {
2443 display: block;
2447 display: block;
2444 margin-bottom: 15px;
2448 margin-bottom: 15px;
2445 }
2449 }
2446
2450
2447 .markup-form textarea {
2451 .markup-form textarea {
2448 width: 100%;
2452 width: 100%;
2449 height: 100px;
2453 height: 100px;
2450 font-family: @text-monospace;
2454 font-family: @text-monospace;
2451 }
2455 }
2452
2456
2453 form.markup-form {
2457 form.markup-form {
2454 margin-top: 10px;
2458 margin-top: 10px;
2455 margin-left: 10px;
2459 margin-left: 10px;
2456 }
2460 }
2457
2461
2458 .markup-form .comment-block-ta,
2462 .markup-form .comment-block-ta,
2459 .markup-form .preview-box {
2463 .markup-form .preview-box {
2460 .border-radius(@border-radius);
2464 .border-radius(@border-radius);
2461 .box-sizing(border-box);
2465 .box-sizing(border-box);
2462 background-color: white;
2466 background-color: white;
2463 }
2467 }
2464
2468
2465 .markup-form .preview-box.unloaded {
2469 .markup-form .preview-box.unloaded {
2466 height: 50px;
2470 height: 50px;
2467 text-align: center;
2471 text-align: center;
2468 padding: 20px;
2472 padding: 20px;
2469 background-color: white;
2473 background-color: white;
2470 }
2474 }
@@ -1,548 +1,552 b''
1 //
1 //
2 // Typography
2 // Typography
3 // modified from Bootstrap
3 // modified from Bootstrap
4 // --------------------------------------------------
4 // --------------------------------------------------
5
5
6 // Base
6 // Base
7 body {
7 body {
8 font-size: @basefontsize;
8 font-size: @basefontsize;
9 font-family: @text-light;
9 font-family: @text-light;
10 letter-spacing: .02em;
10 letter-spacing: .02em;
11 color: @grey2;
11 color: @grey2;
12 }
12 }
13
13
14 #content, label{
14 #content, label{
15 font-size: @basefontsize;
15 font-size: @basefontsize;
16 }
16 }
17
17
18 label {
18 label {
19 color: @grey2;
19 color: @grey2;
20 }
20 }
21
21
22 ::selection { background: @rchighlightblue; }
22 ::selection { background: @rchighlightblue; }
23
23
24 // Headings
24 // Headings
25 // -------------------------
25 // -------------------------
26
26
27 h1, h2, h3, h4, h5, h6,
27 h1, h2, h3, h4, h5, h6,
28 .h1, .h2, .h3, .h4, .h5, .h6 {
28 .h1, .h2, .h3, .h4, .h5, .h6 {
29 margin: 0 0 @textmargin 0;
29 margin: 0 0 @textmargin 0;
30 padding: 0;
30 padding: 0;
31 line-height: 1.8em;
31 line-height: 1.8em;
32 color: @text-color;
32 color: @text-color;
33 a {
33 a {
34 color: @rcblue;
34 color: @rcblue;
35 }
35 }
36 }
36 }
37
37
38 h1, .h1 { font-size: 1.54em; font-weight: @text-bold-weight; font-family: @text-bold; }
38 h1, .h1 { font-size: 1.54em; font-weight: @text-bold-weight; font-family: @text-bold; }
39 h2, .h2 { font-size: 1.23em; font-weight: @text-semibold-weight; font-family: @text-semibold; }
39 h2, .h2 { font-size: 1.23em; font-weight: @text-semibold-weight; font-family: @text-semibold; }
40 h3, .h3 { font-size: 1.23em; font-family: @text-regular; }
40 h3, .h3 { font-size: 1.23em; font-family: @text-regular; }
41 h4, .h4 { font-size: 1em; font-weight: @text-bold-weight; font-family: @text-bold; }
41 h4, .h4 { font-size: 1em; font-weight: @text-bold-weight; font-family: @text-bold; }
42 h5, .h5 { font-size: 1em; font-weight: @text-bold-weight; font-family: @text-bold; }
42 h5, .h5 { font-size: 1em; font-weight: @text-bold-weight; font-family: @text-bold; }
43 h6, .h6 { font-size: 1em; font-weight: @text-bold-weight; font-family: @text-bold; }
43 h6, .h6 { font-size: 1em; font-weight: @text-bold-weight; font-family: @text-bold; }
44
44
45 // Breadcrumbs
45 // Breadcrumbs
46 .breadcrumbs {
46 .breadcrumbs {
47 font-size: @repo-title-fontsize;
47 font-size: @repo-title-fontsize;
48 margin: 0;
48 margin: 0;
49 }
49 }
50
50
51 .breadcrumbs_light {
51 .breadcrumbs_light {
52 float:left;
52 float:left;
53 font-size: 1.3em;
53 font-size: 1.3em;
54 line-height: 38px;
54 line-height: 38px;
55 }
55 }
56
56
57 // Body text
57 // Body text
58 // -------------------------
58 // -------------------------
59
59
60 p {
60 p {
61 margin: 0 0 @textmargin 0;
61 margin: 0 0 @textmargin 0;
62 padding: 0;
62 padding: 0;
63 line-height: 2em;
63 line-height: 2em;
64 }
64 }
65
65
66 .lead {
66 .lead {
67 margin-bottom: @textmargin;
67 margin-bottom: @textmargin;
68 font-weight: 300;
68 font-weight: 300;
69 line-height: 1.4;
69 line-height: 1.4;
70
70
71 @media (min-width: @screen-sm-min) {
71 @media (min-width: @screen-sm-min) {
72 font-size: (@basefontsize * 1.5);
72 font-size: (@basefontsize * 1.5);
73 }
73 }
74 }
74 }
75
75
76 a,
76 a,
77 .link {
77 .link {
78 color: @rcblue;
78 color: @rcblue;
79 text-decoration: none;
79 text-decoration: none;
80 outline: none;
80 outline: none;
81 cursor: pointer;
81 cursor: pointer;
82
82
83 &:focus {
83 &:focus {
84 outline: none;
84 outline: none;
85 }
85 }
86
86
87 &:hover {
87 &:hover {
88 color: @rcdarkblue;
88 color: @rcdarkblue;
89 }
89 }
90 }
90 }
91
91
92 img {
92 img {
93 border: none;
93 border: none;
94 outline: none;
94 outline: none;
95 }
95 }
96
96
97 strong {
97 strong {
98 font-weight: @text-bold-weight;
98 font-weight: @text-bold-weight;
99 font-family: @text-bold;
99 font-family: @text-bold;
100 }
100 }
101
101
102 em {
102 em {
103 font-family: @text-italic;
103 font-family: @text-italic;
104 font-style: italic;
104 font-style: italic;
105 }
105 }
106
106
107 strong em,
107 strong em,
108 em strong {
108 em strong {
109 font-style: italic;
109 font-style: italic;
110 font-weight: @text-bold-italic-weight;
110 font-weight: @text-bold-italic-weight;
111 font-family: @text-bold-italic;
111 font-family: @text-bold-italic;
112 }
112 }
113
113
114 //TODO: lisa: b and i are depreciated, but we are still using them in places.
114 //TODO: lisa: b and i are depreciated, but we are still using them in places.
115 // Should probably make some decision whether to keep or lose these.
115 // Should probably make some decision whether to keep or lose these.
116 b {
116 b {
117
117
118 }
118 }
119
119
120 i {
120 i {
121 font-style: normal;
121 font-style: normal;
122 }
122 }
123
123
124 label {
124 label {
125 color: @text-color;
125 color: @text-color;
126
126
127 input[type="checkbox"] {
127 input[type="checkbox"] {
128 margin-right: 1em;
128 margin-right: 1em;
129 }
129 }
130 input[type="radio"] {
130 input[type="radio"] {
131 margin-right: 1em;
131 margin-right: 1em;
132 }
132 }
133 }
133 }
134
134
135 code,
135 code,
136 .code {
136 .code {
137 font-size: .95em;
137 font-size: .95em;
138 font-family: @text-code;
138 font-family: @text-code;
139 color: @grey3;
139 color: @grey3;
140
140
141 a {
141 a {
142 color: lighten(@rcblue,10%)
142 color: lighten(@rcblue,10%)
143 }
143 }
144 }
144 }
145
145
146 pre {
146 pre {
147 margin: 0;
147 margin: 0;
148 padding: 0;
148 padding: 0;
149 border: 0;
149 border: 0;
150 outline: 0;
150 outline: 0;
151 font-size: @basefontsize*.95;
151 font-size: @basefontsize*.95;
152 line-height: 1.4em;
152 line-height: 1.4em;
153 font-family: @text-code;
153 font-family: @text-code;
154 color: @grey3;
154 color: @grey3;
155 }
155 }
156
156
157 // Emphasis & misc
157 // Emphasis & misc
158 // -------------------------
158 // -------------------------
159
159
160 small,
160 small,
161 .small {
161 .small {
162 font-size: 75%;
162 font-size: 75%;
163 font-weight: normal;
163 font-weight: normal;
164 line-height: 1em;
164 line-height: 1em;
165 }
165 }
166
166
167 mark,
167 mark,
168 .mark {
168 .mark {
169 padding: .2em;
169 padding: .2em;
170 }
170 }
171
171
172 // Alignment
172 // Alignment
173 .text-left { text-align: left; }
173 .text-left { text-align: left; }
174 .text-right { text-align: right; }
174 .text-right { text-align: right; }
175 .text-center { text-align: center; }
175 .text-center { text-align: center; }
176 .text-justify { text-align: justify; }
176 .text-justify { text-align: justify; }
177 .text-nowrap { white-space: nowrap; }
177 .text-nowrap { white-space: nowrap; }
178
178
179 // Transformation
179 // Transformation
180 .text-lowercase { text-transform: lowercase; }
180 .text-lowercase { text-transform: lowercase; }
181 .text-uppercase { text-transform: uppercase; }
181 .text-uppercase { text-transform: uppercase; }
182 .text-capitalize { text-transform: capitalize; }
182 .text-capitalize { text-transform: capitalize; }
183
183
184 // Contextual colors
184 // Contextual colors
185 .text-muted {
185 .text-muted {
186 color: @grey4;
186 color: @grey4;
187 }
187 }
188 .text-primary {
188 .text-primary {
189 color: @rcblue;
189 color: @rcblue;
190 }
190 }
191 .text-success {
191 .text-success {
192 color: @alert1;
192 color: @alert1;
193 }
193 }
194 .text-info {
194 .text-info {
195 color: @alert4;
195 color: @alert4;
196 }
196 }
197 .text-warning {
197 .text-warning {
198 color: @alert3;
198 color: @alert3;
199 }
199 }
200 .text-danger {
200 .text-danger {
201 color: @alert2;
201 color: @alert2;
202 }
202 }
203
203
204 // Contextual backgrounds
204 // Contextual backgrounds
205 .bg-primary {
205 .bg-primary {
206 background-color: white;
206 background-color: white;
207 }
207 }
208 .bg-success {
208 .bg-success {
209 background-color: @alert1;
209 background-color: @alert1;
210 }
210 }
211 .bg-info {
211 .bg-info {
212 background-color: @alert4;
212 background-color: @alert4;
213 }
213 }
214 .bg-warning {
214 .bg-warning {
215 background-color: @alert3;
215 background-color: @alert3;
216 }
216 }
217 .bg-danger {
217 .bg-danger {
218 background-color: @alert2;
218 background-color: @alert2;
219 }
219 }
220
220
221
221
222 // Page header
222 // Page header
223 // -------------------------
223 // -------------------------
224
224
225 .page-header {
225 .page-header {
226 margin: @pagepadding 0 @textmargin;
226 margin: @pagepadding 0 @textmargin;
227 border-bottom: @border-thickness solid @grey5;
227 border-bottom: @border-thickness solid @grey5;
228 }
228 }
229
229
230 .title {
230 .title {
231 clear: both;
231 clear: both;
232 float: left;
232 float: left;
233 width: 100%;
233 width: 100%;
234 margin: @pagepadding/2 0 @pagepadding;
234 margin: @pagepadding/2 0 @pagepadding;
235
235
236 .breadcrumbs {
236 .breadcrumbs {
237 float: left;
237 float: left;
238 clear: both;
238 clear: both;
239 width: 700px;
239 width: 700px;
240 margin: 0;
240 margin: 0;
241
241
242 .q_filter_box {
242 .q_filter_box {
243 margin-right: @padding;
243 margin-right: @padding;
244 }
244 }
245 }
245 }
246
246
247 h1 a {
247 h1 a {
248 color: @rcblue;
248 color: @rcblue;
249 }
249 }
250
250
251 input{
251 input{
252 margin-right: @padding;
252 margin-right: @padding;
253 }
253 }
254
254
255 h5, .h5 {
255 h5, .h5 {
256 color: @grey1;
256 color: @grey1;
257 margin-bottom: @space;
257 margin-bottom: @space;
258
258
259 span {
259 span {
260 display: inline-block;
260 display: inline-block;
261 }
261 }
262 }
262 }
263
263
264 p {
264 p {
265 margin-bottom: 0;
265 margin-bottom: 0;
266 }
266 }
267
267
268 .links {
268 .links {
269 float: right;
269 float: right;
270 display: inline;
270 display: inline;
271 margin: 0;
271 margin: 0;
272 padding-left: 0;
272 padding-left: 0;
273 list-style: none;
273 list-style: none;
274 text-align: right;
274 text-align: right;
275
275
276 li {
276 li {
277 float: right;
277 float: right;
278 list-style-type: none;
278 list-style-type: none;
279 }
279 }
280
280
281 a {
281 a {
282 display: inline-block;
282 display: inline-block;
283 margin-left: @textmargin/2;
283 margin-left: @textmargin/2;
284 }
284 }
285 }
285 }
286
286
287 .title-content {
287 .title-content {
288 float: left;
288 float: left;
289 margin: 0;
289 margin: 0;
290 padding: 0;
290 padding: 0;
291
291
292 & + .breadcrumbs {
292 & + .breadcrumbs {
293 margin-top: @padding;
293 margin-top: @padding;
294 }
294 }
295
295
296 & + .links {
296 & + .links {
297 margin-top: -@button-padding;
297 margin-top: -@button-padding;
298
298
299 & + .breadcrumbs {
299 & + .breadcrumbs {
300 margin-top: @padding;
300 margin-top: @padding;
301 }
301 }
302 }
302 }
303
304 .repo-group-desc {
305 padding: 8px 0px 0px 0px;
306 }
303 }
307 }
304
308
305 .title-main {
309 .title-main {
306 font-size: @repo-title-fontsize;
310 font-size: @repo-title-fontsize;
307 }
311 }
308
312
309 .title-description {
313 .title-description {
310 margin-top: .5em;
314 margin-top: .5em;
311 }
315 }
312
316
313 .q_filter_box {
317 .q_filter_box {
314 width: 200px;
318 width: 200px;
315 }
319 }
316
320
317 }
321 }
318
322
319 #readme .title {
323 #readme .title {
320 text-transform: none;
324 text-transform: none;
321 }
325 }
322
326
323 // Lists
327 // Lists
324 // -------------------------
328 // -------------------------
325
329
326 // Unordered and Ordered lists
330 // Unordered and Ordered lists
327 ul,
331 ul,
328 ol {
332 ol {
329 margin-top: 0;
333 margin-top: 0;
330 margin-bottom: @textmargin;
334 margin-bottom: @textmargin;
331 ul,
335 ul,
332 ol {
336 ol {
333 margin-bottom: 0;
337 margin-bottom: 0;
334 }
338 }
335 }
339 }
336
340
337 li {
341 li {
338 line-height: 2em;
342 line-height: 2em;
339 }
343 }
340
344
341 ul li {
345 ul li {
342 position: relative;
346 position: relative;
343 list-style-type: disc;
347 list-style-type: disc;
344
348
345 p:first-child {
349 p:first-child {
346 display:inline;
350 display:inline;
347 }
351 }
348 }
352 }
349
353
350 // List options
354 // List options
351
355
352 // Unstyled keeps list items block level, just removes default browser padding and list-style
356 // Unstyled keeps list items block level, just removes default browser padding and list-style
353 .list-unstyled {
357 .list-unstyled {
354 padding-left: 0;
358 padding-left: 0;
355 list-style: none;
359 list-style: none;
356 li:before { content: none; }
360 li:before { content: none; }
357 }
361 }
358
362
359 // Inline turns list items into inline-block
363 // Inline turns list items into inline-block
360 .list-inline {
364 .list-inline {
361 .list-unstyled();
365 .list-unstyled();
362 margin-left: -5px;
366 margin-left: -5px;
363
367
364 > li {
368 > li {
365 display: inline-block;
369 display: inline-block;
366 padding-left: 5px;
370 padding-left: 5px;
367 padding-right: 5px;
371 padding-right: 5px;
368 }
372 }
369 }
373 }
370
374
371 // Description Lists
375 // Description Lists
372
376
373 dl {
377 dl {
374 margin-top: 0; // Remove browser default
378 margin-top: 0; // Remove browser default
375 margin-bottom: @textmargin;
379 margin-bottom: @textmargin;
376 }
380 }
377
381
378 dt,
382 dt,
379 dd {
383 dd {
380 line-height: 1.4em;
384 line-height: 1.4em;
381 }
385 }
382
386
383 dt {
387 dt {
384 margin: @textmargin 0 0 0;
388 margin: @textmargin 0 0 0;
385 font-weight: @text-bold-weight;
389 font-weight: @text-bold-weight;
386 font-family: @text-bold;
390 font-family: @text-bold;
387 }
391 }
388
392
389 dd {
393 dd {
390 margin-left: 0; // Undo browser default
394 margin-left: 0; // Undo browser default
391 }
395 }
392
396
393 // Horizontal description lists
397 // Horizontal description lists
394 // Defaults to being stacked without any of the below styles applied, until the
398 // Defaults to being stacked without any of the below styles applied, until the
395 // grid breakpoint is reached (default of ~768px).
399 // grid breakpoint is reached (default of ~768px).
396 // These are used in forms as well; see style guide.
400 // These are used in forms as well; see style guide.
397 // TODO: lisa: These should really not be used in forms.
401 // TODO: lisa: These should really not be used in forms.
398
402
399 .dl-horizontal {
403 .dl-horizontal {
400
404
401 overflow: hidden;
405 overflow: hidden;
402 margin-bottom: @space;
406 margin-bottom: @space;
403
407
404 dt, dd {
408 dt, dd {
405 float: left;
409 float: left;
406 margin: 5px 0 5px 0;
410 margin: 5px 0 5px 0;
407 }
411 }
408
412
409 dt {
413 dt {
410 clear: left;
414 clear: left;
411 width: @label-width - @form-vertical-margin;
415 width: @label-width - @form-vertical-margin;
412 }
416 }
413
417
414 dd {
418 dd {
415 &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present
419 &:extend(.clearfix all); // Clear the floated `dt` if an empty `dd` is present
416 margin-left: @form-vertical-margin;
420 margin-left: @form-vertical-margin;
417 max-width: @form-max-width - (@label-width - @form-vertical-margin) - @form-vertical-margin;
421 max-width: @form-max-width - (@label-width - @form-vertical-margin) - @form-vertical-margin;
418 }
422 }
419
423
420 pre {
424 pre {
421 margin: 0;
425 margin: 0;
422 }
426 }
423
427
424 &.settings {
428 &.settings {
425 dt {
429 dt {
426 text-align: left;
430 text-align: left;
427 }
431 }
428 }
432 }
429
433
430 @media (min-width: 768px) {
434 @media (min-width: 768px) {
431 dt {
435 dt {
432 float: left;
436 float: left;
433 width: 185px;
437 width: 185px;
434 clear: left;
438 clear: left;
435 text-align: right;
439 text-align: right;
436 }
440 }
437 dd {
441 dd {
438 margin-left: 20px;
442 margin-left: 20px;
439 }
443 }
440 }
444 }
441 }
445 }
442
446
443
447
444 // Misc
448 // Misc
445 // -------------------------
449 // -------------------------
446
450
447 // Abbreviations and acronyms
451 // Abbreviations and acronyms
448 abbr[title],
452 abbr[title],
449 abbr[data-original-title] {
453 abbr[data-original-title] {
450 cursor: help;
454 cursor: help;
451 border-bottom: @border-thickness dotted @grey4;
455 border-bottom: @border-thickness dotted @grey4;
452 }
456 }
453 .initialism {
457 .initialism {
454 font-size: 90%;
458 font-size: 90%;
455 text-transform: uppercase;
459 text-transform: uppercase;
456 }
460 }
457
461
458 // Blockquotes
462 // Blockquotes
459 blockquote {
463 blockquote {
460 padding: 1em 2em;
464 padding: 1em 2em;
461 margin: 0 0 2em;
465 margin: 0 0 2em;
462 font-size: @basefontsize;
466 font-size: @basefontsize;
463 border-left: 2px solid @grey6;
467 border-left: 2px solid @grey6;
464
468
465 p,
469 p,
466 ul,
470 ul,
467 ol {
471 ol {
468 &:last-child {
472 &:last-child {
469 margin-bottom: 0;
473 margin-bottom: 0;
470 }
474 }
471 }
475 }
472
476
473 footer,
477 footer,
474 small,
478 small,
475 .small {
479 .small {
476 display: block;
480 display: block;
477 font-size: 80%;
481 font-size: 80%;
478
482
479 &:before {
483 &:before {
480 content: '\2014 \00A0'; // em dash, nbsp
484 content: '\2014 \00A0'; // em dash, nbsp
481 }
485 }
482 }
486 }
483 }
487 }
484
488
485 // Opposite alignment of blockquote
489 // Opposite alignment of blockquote
486 //
490 //
487 .blockquote-reverse,
491 .blockquote-reverse,
488 blockquote.pull-right {
492 blockquote.pull-right {
489 padding-right: 15px;
493 padding-right: 15px;
490 padding-left: 0;
494 padding-left: 0;
491 border-right: 5px solid @grey6;
495 border-right: 5px solid @grey6;
492 border-left: 0;
496 border-left: 0;
493 text-align: right;
497 text-align: right;
494
498
495 // Account for citation
499 // Account for citation
496 footer,
500 footer,
497 small,
501 small,
498 .small {
502 .small {
499 &:before { content: ''; }
503 &:before { content: ''; }
500 &:after {
504 &:after {
501 content: '\00A0 \2014'; // nbsp, em dash
505 content: '\00A0 \2014'; // nbsp, em dash
502 }
506 }
503 }
507 }
504 }
508 }
505
509
506 // Addresses
510 // Addresses
507 address {
511 address {
508 margin-bottom: 2em;
512 margin-bottom: 2em;
509 font-style: normal;
513 font-style: normal;
510 line-height: 1.8em;
514 line-height: 1.8em;
511 }
515 }
512
516
513 .error-message {
517 .error-message {
514 display: block;
518 display: block;
515 margin: @padding/3 0;
519 margin: @padding/3 0;
516 color: @alert2;
520 color: @alert2;
517 }
521 }
518
522
519 .issue-tracker-link {
523 .issue-tracker-link {
520 color: @rcblue;
524 color: @rcblue;
521 }
525 }
522
526
523 .info_text{
527 .info_text{
524 font-size: @basefontsize;
528 font-size: @basefontsize;
525 color: @grey4;
529 color: @grey4;
526 font-family: @text-regular;
530 font-family: @text-regular;
527 }
531 }
528
532
529 .help-block-inline {
533 .help-block-inline {
530 margin: 0;
534 margin: 0;
531 }
535 }
532
536
533 // help block text
537 // help block text
534 .help-block {
538 .help-block {
535 display: block;
539 display: block;
536 margin: 0 0 @padding;
540 margin: 0 0 @padding;
537 color: @grey4;
541 color: @grey4;
538 font-family: @text-light;
542 font-family: @text-light;
539 &.pre-formatting {
543 &.pre-formatting {
540 white-space: pre-wrap;
544 white-space: pre-wrap;
541 }
545 }
542 }
546 }
543
547
544 .error-message {
548 .error-message {
545 display: block;
549 display: block;
546 margin: @padding/3 0;
550 margin: @padding/3 0;
547 color: @alert2;
551 color: @alert2;
548 }
552 }
@@ -1,362 +1,364 b''
1
1
2 /******************************************************************************
2 /******************************************************************************
3 * *
3 * *
4 * DO NOT CHANGE THIS FILE MANUALLY *
4 * DO NOT CHANGE THIS FILE MANUALLY *
5 * *
5 * *
6 * *
6 * *
7 * This file is automatically generated when the app starts up with *
7 * This file is automatically generated when the app starts up with *
8 * generate_js_files = true *
8 * generate_js_files = true *
9 * *
9 * *
10 * To add a route here pass jsroute=True to the route definition in the app *
10 * To add a route here pass jsroute=True to the route definition in the app *
11 * *
11 * *
12 ******************************************************************************/
12 ******************************************************************************/
13 function registerRCRoutes() {
13 function registerRCRoutes() {
14 // routes registration
14 // routes registration
15 pyroutes.register('favicon', '/favicon.ico', []);
15 pyroutes.register('favicon', '/favicon.ico', []);
16 pyroutes.register('robots', '/robots.txt', []);
16 pyroutes.register('robots', '/robots.txt', []);
17 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
17 pyroutes.register('global_integrations_new', '/_admin/integrations/new', []);
18 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
18 pyroutes.register('global_integrations_home', '/_admin/integrations', []);
19 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
19 pyroutes.register('global_integrations_list', '/_admin/integrations/%(integration)s', ['integration']);
20 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
20 pyroutes.register('global_integrations_create', '/_admin/integrations/%(integration)s/new', ['integration']);
21 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
21 pyroutes.register('global_integrations_edit', '/_admin/integrations/%(integration)s/%(integration_id)s', ['integration', 'integration_id']);
22 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
22 pyroutes.register('repo_group_integrations_home', '/%(repo_group_name)s/_settings/integrations', ['repo_group_name']);
23 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
23 pyroutes.register('repo_group_integrations_new', '/%(repo_group_name)s/_settings/integrations/new', ['repo_group_name']);
24 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
24 pyroutes.register('repo_group_integrations_list', '/%(repo_group_name)s/_settings/integrations/%(integration)s', ['repo_group_name', 'integration']);
25 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
25 pyroutes.register('repo_group_integrations_create', '/%(repo_group_name)s/_settings/integrations/%(integration)s/new', ['repo_group_name', 'integration']);
26 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
26 pyroutes.register('repo_group_integrations_edit', '/%(repo_group_name)s/_settings/integrations/%(integration)s/%(integration_id)s', ['repo_group_name', 'integration', 'integration_id']);
27 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
27 pyroutes.register('repo_integrations_home', '/%(repo_name)s/settings/integrations', ['repo_name']);
28 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
28 pyroutes.register('repo_integrations_new', '/%(repo_name)s/settings/integrations/new', ['repo_name']);
29 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
29 pyroutes.register('repo_integrations_list', '/%(repo_name)s/settings/integrations/%(integration)s', ['repo_name', 'integration']);
30 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
30 pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']);
31 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
31 pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']);
32 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
32 pyroutes.register('auth_home', '/_admin/auth*traverse', []);
33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
33 pyroutes.register('ops_ping', '/_admin/ops/ping', []);
34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
34 pyroutes.register('ops_error_test', '/_admin/ops/error', []);
35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
35 pyroutes.register('ops_redirect_test', '/_admin/ops/redirect', []);
36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
36 pyroutes.register('ops_ping_legacy', '/_admin/ping', []);
37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
37 pyroutes.register('ops_error_test_legacy', '/_admin/error_test', []);
38 pyroutes.register('admin_home', '/_admin', []);
38 pyroutes.register('admin_home', '/_admin', []);
39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
39 pyroutes.register('admin_audit_logs', '/_admin/audit_logs', []);
40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
40 pyroutes.register('admin_audit_log_entry', '/_admin/audit_logs/%(audit_log_id)s', ['audit_log_id']);
41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
41 pyroutes.register('pull_requests_global_0', '/_admin/pull_requests/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
42 pyroutes.register('pull_requests_global_1', '/_admin/pull-requests/%(pull_request_id)s', ['pull_request_id']);
43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
43 pyroutes.register('pull_requests_global', '/_admin/pull-request/%(pull_request_id)s', ['pull_request_id']);
44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
44 pyroutes.register('admin_settings_open_source', '/_admin/settings/open_source', []);
45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
45 pyroutes.register('admin_settings_vcs_svn_generate_cfg', '/_admin/settings/vcs/svn_generate_cfg', []);
46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
46 pyroutes.register('admin_settings_system', '/_admin/settings/system', []);
47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
47 pyroutes.register('admin_settings_system_update', '/_admin/settings/system/updates', []);
48 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
48 pyroutes.register('admin_settings_exception_tracker', '/_admin/settings/exceptions', []);
49 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
49 pyroutes.register('admin_settings_exception_tracker_delete_all', '/_admin/settings/exceptions/delete', []);
50 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
50 pyroutes.register('admin_settings_exception_tracker_show', '/_admin/settings/exceptions/%(exception_id)s', ['exception_id']);
51 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
51 pyroutes.register('admin_settings_exception_tracker_delete', '/_admin/settings/exceptions/%(exception_id)s/delete', ['exception_id']);
52 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
52 pyroutes.register('admin_settings_sessions', '/_admin/settings/sessions', []);
53 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
53 pyroutes.register('admin_settings_sessions_cleanup', '/_admin/settings/sessions/cleanup', []);
54 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
54 pyroutes.register('admin_settings_process_management', '/_admin/settings/process_management', []);
55 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
55 pyroutes.register('admin_settings_process_management_data', '/_admin/settings/process_management/data', []);
56 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
56 pyroutes.register('admin_settings_process_management_signal', '/_admin/settings/process_management/signal', []);
57 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
57 pyroutes.register('admin_settings_process_management_master_signal', '/_admin/settings/process_management/master_signal', []);
58 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
58 pyroutes.register('admin_defaults_repositories', '/_admin/defaults/repositories', []);
59 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
59 pyroutes.register('admin_defaults_repositories_update', '/_admin/defaults/repositories/update', []);
60 pyroutes.register('admin_settings', '/_admin/settings', []);
60 pyroutes.register('admin_settings', '/_admin/settings', []);
61 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
61 pyroutes.register('admin_settings_update', '/_admin/settings/update', []);
62 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
62 pyroutes.register('admin_settings_global', '/_admin/settings/global', []);
63 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
63 pyroutes.register('admin_settings_global_update', '/_admin/settings/global/update', []);
64 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
64 pyroutes.register('admin_settings_vcs', '/_admin/settings/vcs', []);
65 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
65 pyroutes.register('admin_settings_vcs_update', '/_admin/settings/vcs/update', []);
66 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
66 pyroutes.register('admin_settings_vcs_svn_pattern_delete', '/_admin/settings/vcs/svn_pattern_delete', []);
67 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
67 pyroutes.register('admin_settings_mapping', '/_admin/settings/mapping', []);
68 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
68 pyroutes.register('admin_settings_mapping_update', '/_admin/settings/mapping/update', []);
69 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
69 pyroutes.register('admin_settings_visual', '/_admin/settings/visual', []);
70 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
70 pyroutes.register('admin_settings_visual_update', '/_admin/settings/visual/update', []);
71 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
71 pyroutes.register('admin_settings_issuetracker', '/_admin/settings/issue-tracker', []);
72 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
72 pyroutes.register('admin_settings_issuetracker_update', '/_admin/settings/issue-tracker/update', []);
73 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
73 pyroutes.register('admin_settings_issuetracker_test', '/_admin/settings/issue-tracker/test', []);
74 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
74 pyroutes.register('admin_settings_issuetracker_delete', '/_admin/settings/issue-tracker/delete', []);
75 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
75 pyroutes.register('admin_settings_email', '/_admin/settings/email', []);
76 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
76 pyroutes.register('admin_settings_email_update', '/_admin/settings/email/update', []);
77 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
77 pyroutes.register('admin_settings_hooks', '/_admin/settings/hooks', []);
78 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
78 pyroutes.register('admin_settings_hooks_update', '/_admin/settings/hooks/update', []);
79 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
79 pyroutes.register('admin_settings_hooks_delete', '/_admin/settings/hooks/delete', []);
80 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
80 pyroutes.register('admin_settings_search', '/_admin/settings/search', []);
81 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
81 pyroutes.register('admin_settings_labs', '/_admin/settings/labs', []);
82 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
82 pyroutes.register('admin_settings_labs_update', '/_admin/settings/labs/update', []);
83 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
83 pyroutes.register('admin_permissions_application', '/_admin/permissions/application', []);
84 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
84 pyroutes.register('admin_permissions_application_update', '/_admin/permissions/application/update', []);
85 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
85 pyroutes.register('admin_permissions_global', '/_admin/permissions/global', []);
86 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
86 pyroutes.register('admin_permissions_global_update', '/_admin/permissions/global/update', []);
87 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
87 pyroutes.register('admin_permissions_object', '/_admin/permissions/object', []);
88 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
88 pyroutes.register('admin_permissions_object_update', '/_admin/permissions/object/update', []);
89 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
89 pyroutes.register('admin_permissions_ips', '/_admin/permissions/ips', []);
90 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
90 pyroutes.register('admin_permissions_overview', '/_admin/permissions/overview', []);
91 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
91 pyroutes.register('admin_permissions_auth_token_access', '/_admin/permissions/auth_token_access', []);
92 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
92 pyroutes.register('admin_permissions_ssh_keys', '/_admin/permissions/ssh_keys', []);
93 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
93 pyroutes.register('admin_permissions_ssh_keys_data', '/_admin/permissions/ssh_keys/data', []);
94 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
94 pyroutes.register('admin_permissions_ssh_keys_update', '/_admin/permissions/ssh_keys/update', []);
95 pyroutes.register('users', '/_admin/users', []);
95 pyroutes.register('users', '/_admin/users', []);
96 pyroutes.register('users_data', '/_admin/users_data', []);
96 pyroutes.register('users_data', '/_admin/users_data', []);
97 pyroutes.register('users_create', '/_admin/users/create', []);
97 pyroutes.register('users_create', '/_admin/users/create', []);
98 pyroutes.register('users_new', '/_admin/users/new', []);
98 pyroutes.register('users_new', '/_admin/users/new', []);
99 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
99 pyroutes.register('user_edit', '/_admin/users/%(user_id)s/edit', ['user_id']);
100 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
100 pyroutes.register('user_edit_advanced', '/_admin/users/%(user_id)s/edit/advanced', ['user_id']);
101 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
101 pyroutes.register('user_edit_global_perms', '/_admin/users/%(user_id)s/edit/global_permissions', ['user_id']);
102 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
102 pyroutes.register('user_edit_global_perms_update', '/_admin/users/%(user_id)s/edit/global_permissions/update', ['user_id']);
103 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
103 pyroutes.register('user_update', '/_admin/users/%(user_id)s/update', ['user_id']);
104 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
104 pyroutes.register('user_delete', '/_admin/users/%(user_id)s/delete', ['user_id']);
105 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
105 pyroutes.register('user_enable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_enable', ['user_id']);
106 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
106 pyroutes.register('user_disable_force_password_reset', '/_admin/users/%(user_id)s/password_reset_disable', ['user_id']);
107 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
107 pyroutes.register('user_create_personal_repo_group', '/_admin/users/%(user_id)s/create_repo_group', ['user_id']);
108 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
108 pyroutes.register('edit_user_auth_tokens_delete', '/_admin/users/%(user_id)s/edit/auth_tokens/delete', ['user_id']);
109 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
109 pyroutes.register('edit_user_ssh_keys', '/_admin/users/%(user_id)s/edit/ssh_keys', ['user_id']);
110 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
110 pyroutes.register('edit_user_ssh_keys_generate_keypair', '/_admin/users/%(user_id)s/edit/ssh_keys/generate', ['user_id']);
111 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
111 pyroutes.register('edit_user_ssh_keys_add', '/_admin/users/%(user_id)s/edit/ssh_keys/new', ['user_id']);
112 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
112 pyroutes.register('edit_user_ssh_keys_delete', '/_admin/users/%(user_id)s/edit/ssh_keys/delete', ['user_id']);
113 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
113 pyroutes.register('edit_user_emails', '/_admin/users/%(user_id)s/edit/emails', ['user_id']);
114 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
114 pyroutes.register('edit_user_emails_add', '/_admin/users/%(user_id)s/edit/emails/new', ['user_id']);
115 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
115 pyroutes.register('edit_user_emails_delete', '/_admin/users/%(user_id)s/edit/emails/delete', ['user_id']);
116 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
116 pyroutes.register('edit_user_ips', '/_admin/users/%(user_id)s/edit/ips', ['user_id']);
117 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
117 pyroutes.register('edit_user_ips_add', '/_admin/users/%(user_id)s/edit/ips/new', ['user_id']);
118 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
118 pyroutes.register('edit_user_ips_delete', '/_admin/users/%(user_id)s/edit/ips/delete', ['user_id']);
119 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
119 pyroutes.register('edit_user_perms_summary', '/_admin/users/%(user_id)s/edit/permissions_summary', ['user_id']);
120 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
120 pyroutes.register('edit_user_perms_summary_json', '/_admin/users/%(user_id)s/edit/permissions_summary/json', ['user_id']);
121 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
121 pyroutes.register('edit_user_groups_management', '/_admin/users/%(user_id)s/edit/groups_management', ['user_id']);
122 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
122 pyroutes.register('edit_user_groups_management_updates', '/_admin/users/%(user_id)s/edit/edit_user_groups_management/updates', ['user_id']);
123 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
123 pyroutes.register('edit_user_audit_logs', '/_admin/users/%(user_id)s/edit/audit', ['user_id']);
124 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
124 pyroutes.register('edit_user_caches', '/_admin/users/%(user_id)s/edit/caches', ['user_id']);
125 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
125 pyroutes.register('edit_user_caches_update', '/_admin/users/%(user_id)s/edit/caches/update', ['user_id']);
126 pyroutes.register('user_groups', '/_admin/user_groups', []);
126 pyroutes.register('user_groups', '/_admin/user_groups', []);
127 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
127 pyroutes.register('user_groups_data', '/_admin/user_groups_data', []);
128 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
128 pyroutes.register('user_groups_new', '/_admin/user_groups/new', []);
129 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
129 pyroutes.register('user_groups_create', '/_admin/user_groups/create', []);
130 pyroutes.register('repos', '/_admin/repos', []);
130 pyroutes.register('repos', '/_admin/repos', []);
131 pyroutes.register('repo_new', '/_admin/repos/new', []);
131 pyroutes.register('repo_new', '/_admin/repos/new', []);
132 pyroutes.register('repo_create', '/_admin/repos/create', []);
132 pyroutes.register('repo_create', '/_admin/repos/create', []);
133 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
133 pyroutes.register('repo_groups', '/_admin/repo_groups', []);
134 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
134 pyroutes.register('repo_group_new', '/_admin/repo_group/new', []);
135 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
135 pyroutes.register('repo_group_create', '/_admin/repo_group/create', []);
136 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
136 pyroutes.register('channelstream_connect', '/_admin/channelstream/connect', []);
137 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
137 pyroutes.register('channelstream_subscribe', '/_admin/channelstream/subscribe', []);
138 pyroutes.register('channelstream_proxy', '/_channelstream', []);
138 pyroutes.register('channelstream_proxy', '/_channelstream', []);
139 pyroutes.register('upload_file', '/_file_store/upload', []);
139 pyroutes.register('upload_file', '/_file_store/upload', []);
140 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
140 pyroutes.register('download_file', '/_file_store/download/%(fid)s', ['fid']);
141 pyroutes.register('logout', '/_admin/logout', []);
141 pyroutes.register('logout', '/_admin/logout', []);
142 pyroutes.register('reset_password', '/_admin/password_reset', []);
142 pyroutes.register('reset_password', '/_admin/password_reset', []);
143 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
143 pyroutes.register('reset_password_confirmation', '/_admin/password_reset_confirmation', []);
144 pyroutes.register('home', '/', []);
144 pyroutes.register('home', '/', []);
145 pyroutes.register('user_autocomplete_data', '/_users', []);
145 pyroutes.register('user_autocomplete_data', '/_users', []);
146 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
146 pyroutes.register('user_group_autocomplete_data', '/_user_groups', []);
147 pyroutes.register('repo_list_data', '/_repos', []);
147 pyroutes.register('repo_list_data', '/_repos', []);
148 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
148 pyroutes.register('repo_group_list_data', '/_repo_groups', []);
149 pyroutes.register('goto_switcher_data', '/_goto_data', []);
149 pyroutes.register('goto_switcher_data', '/_goto_data', []);
150 pyroutes.register('markup_preview', '/_markup_preview', []);
150 pyroutes.register('markup_preview', '/_markup_preview', []);
151 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
151 pyroutes.register('store_user_session_value', '/_store_session_attr', []);
152 pyroutes.register('journal', '/_admin/journal', []);
152 pyroutes.register('journal', '/_admin/journal', []);
153 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
153 pyroutes.register('journal_rss', '/_admin/journal/rss', []);
154 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
154 pyroutes.register('journal_atom', '/_admin/journal/atom', []);
155 pyroutes.register('journal_public', '/_admin/public_journal', []);
155 pyroutes.register('journal_public', '/_admin/public_journal', []);
156 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
156 pyroutes.register('journal_public_atom', '/_admin/public_journal/atom', []);
157 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
157 pyroutes.register('journal_public_atom_old', '/_admin/public_journal_atom', []);
158 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
158 pyroutes.register('journal_public_rss', '/_admin/public_journal/rss', []);
159 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
159 pyroutes.register('journal_public_rss_old', '/_admin/public_journal_rss', []);
160 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
160 pyroutes.register('toggle_following', '/_admin/toggle_following', []);
161 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
161 pyroutes.register('repo_creating', '/%(repo_name)s/repo_creating', ['repo_name']);
162 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
162 pyroutes.register('repo_creating_check', '/%(repo_name)s/repo_creating_check', ['repo_name']);
163 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
163 pyroutes.register('repo_summary_explicit', '/%(repo_name)s/summary', ['repo_name']);
164 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
164 pyroutes.register('repo_summary_commits', '/%(repo_name)s/summary-commits', ['repo_name']);
165 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
165 pyroutes.register('repo_commit', '/%(repo_name)s/changeset/%(commit_id)s', ['repo_name', 'commit_id']);
166 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
166 pyroutes.register('repo_commit_children', '/%(repo_name)s/changeset_children/%(commit_id)s', ['repo_name', 'commit_id']);
167 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
167 pyroutes.register('repo_commit_parents', '/%(repo_name)s/changeset_parents/%(commit_id)s', ['repo_name', 'commit_id']);
168 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
168 pyroutes.register('repo_commit_raw', '/%(repo_name)s/changeset-diff/%(commit_id)s', ['repo_name', 'commit_id']);
169 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
169 pyroutes.register('repo_commit_patch', '/%(repo_name)s/changeset-patch/%(commit_id)s', ['repo_name', 'commit_id']);
170 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
170 pyroutes.register('repo_commit_download', '/%(repo_name)s/changeset-download/%(commit_id)s', ['repo_name', 'commit_id']);
171 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
171 pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']);
172 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
172 pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']);
173 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
173 pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']);
174 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
174 pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']);
175 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
175 pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']);
176 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
176 pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']);
177 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
177 pyroutes.register('repo_files_diff', '/%(repo_name)s/diff/%(f_path)s', ['repo_name', 'f_path']);
178 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
178 pyroutes.register('repo_files_diff_2way_redirect', '/%(repo_name)s/diff-2way/%(f_path)s', ['repo_name', 'f_path']);
179 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
179 pyroutes.register('repo_files', '/%(repo_name)s/files/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
180 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
180 pyroutes.register('repo_files:default_path', '/%(repo_name)s/files/%(commit_id)s/', ['repo_name', 'commit_id']);
181 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
181 pyroutes.register('repo_files:default_commit', '/%(repo_name)s/files', ['repo_name']);
182 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
182 pyroutes.register('repo_files:rendered', '/%(repo_name)s/render/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
183 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
183 pyroutes.register('repo_files:annotated', '/%(repo_name)s/annotate/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
184 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
184 pyroutes.register('repo_files:annotated_previous', '/%(repo_name)s/annotate-previous/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
185 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
185 pyroutes.register('repo_nodetree_full', '/%(repo_name)s/nodetree_full/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
186 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
186 pyroutes.register('repo_nodetree_full:default_path', '/%(repo_name)s/nodetree_full/%(commit_id)s/', ['repo_name', 'commit_id']);
187 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
187 pyroutes.register('repo_files_nodelist', '/%(repo_name)s/nodelist/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
188 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
188 pyroutes.register('repo_file_raw', '/%(repo_name)s/raw/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
189 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
189 pyroutes.register('repo_file_download', '/%(repo_name)s/download/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
190 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
190 pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
191 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
191 pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
192 pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
193 pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
194 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
194 pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
195 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
195 pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
196 pyroutes.register('repo_files_update_file', '/%(repo_name)s/update_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
197 pyroutes.register('repo_files_add_file', '/%(repo_name)s/add_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
198 pyroutes.register('repo_files_create_file', '/%(repo_name)s/create_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
199 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
199 pyroutes.register('repo_refs_data', '/%(repo_name)s/refs-data', ['repo_name']);
200 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
200 pyroutes.register('repo_refs_changelog_data', '/%(repo_name)s/refs-data-changelog', ['repo_name']);
201 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
201 pyroutes.register('repo_stats', '/%(repo_name)s/repo_stats/%(commit_id)s', ['repo_name', 'commit_id']);
202 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
202 pyroutes.register('repo_changelog', '/%(repo_name)s/changelog', ['repo_name']);
203 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
203 pyroutes.register('repo_changelog_file', '/%(repo_name)s/changelog/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
204 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
204 pyroutes.register('repo_changelog_elements', '/%(repo_name)s/changelog_elements', ['repo_name']);
205 pyroutes.register('repo_changelog_elements_file', '/%(repo_name)s/changelog_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
205 pyroutes.register('repo_changelog_elements_file', '/%(repo_name)s/changelog_elements/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
206 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
206 pyroutes.register('repo_compare_select', '/%(repo_name)s/compare', ['repo_name']);
207 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']);
207 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']);
208 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
208 pyroutes.register('tags_home', '/%(repo_name)s/tags', ['repo_name']);
209 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
209 pyroutes.register('branches_home', '/%(repo_name)s/branches', ['repo_name']);
210 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
210 pyroutes.register('bookmarks_home', '/%(repo_name)s/bookmarks', ['repo_name']);
211 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
211 pyroutes.register('repo_fork_new', '/%(repo_name)s/fork', ['repo_name']);
212 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
212 pyroutes.register('repo_fork_create', '/%(repo_name)s/fork/create', ['repo_name']);
213 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
213 pyroutes.register('repo_forks_show_all', '/%(repo_name)s/forks', ['repo_name']);
214 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
214 pyroutes.register('repo_forks_data', '/%(repo_name)s/forks/data', ['repo_name']);
215 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
215 pyroutes.register('pullrequest_show', '/%(repo_name)s/pull-request/%(pull_request_id)s', ['repo_name', 'pull_request_id']);
216 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
216 pyroutes.register('pullrequest_show_all', '/%(repo_name)s/pull-request', ['repo_name']);
217 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
217 pyroutes.register('pullrequest_show_all_data', '/%(repo_name)s/pull-request-data', ['repo_name']);
218 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
218 pyroutes.register('pullrequest_repo_refs', '/%(repo_name)s/pull-request/refs/%(target_repo_name)s', ['repo_name', 'target_repo_name']);
219 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
219 pyroutes.register('pullrequest_repo_targets', '/%(repo_name)s/pull-request/repo-targets', ['repo_name']);
220 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
220 pyroutes.register('pullrequest_new', '/%(repo_name)s/pull-request/new', ['repo_name']);
221 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
221 pyroutes.register('pullrequest_create', '/%(repo_name)s/pull-request/create', ['repo_name']);
222 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
222 pyroutes.register('pullrequest_update', '/%(repo_name)s/pull-request/%(pull_request_id)s/update', ['repo_name', 'pull_request_id']);
223 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
223 pyroutes.register('pullrequest_merge', '/%(repo_name)s/pull-request/%(pull_request_id)s/merge', ['repo_name', 'pull_request_id']);
224 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
224 pyroutes.register('pullrequest_delete', '/%(repo_name)s/pull-request/%(pull_request_id)s/delete', ['repo_name', 'pull_request_id']);
225 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
225 pyroutes.register('pullrequest_comment_create', '/%(repo_name)s/pull-request/%(pull_request_id)s/comment', ['repo_name', 'pull_request_id']);
226 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']);
226 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']);
227 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
227 pyroutes.register('edit_repo', '/%(repo_name)s/settings', ['repo_name']);
228 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
228 pyroutes.register('edit_repo_advanced', '/%(repo_name)s/settings/advanced', ['repo_name']);
229 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
229 pyroutes.register('edit_repo_advanced_archive', '/%(repo_name)s/settings/advanced/archive', ['repo_name']);
230 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
230 pyroutes.register('edit_repo_advanced_delete', '/%(repo_name)s/settings/advanced/delete', ['repo_name']);
231 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
231 pyroutes.register('edit_repo_advanced_locking', '/%(repo_name)s/settings/advanced/locking', ['repo_name']);
232 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
232 pyroutes.register('edit_repo_advanced_journal', '/%(repo_name)s/settings/advanced/journal', ['repo_name']);
233 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
233 pyroutes.register('edit_repo_advanced_fork', '/%(repo_name)s/settings/advanced/fork', ['repo_name']);
234 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
234 pyroutes.register('edit_repo_advanced_hooks', '/%(repo_name)s/settings/advanced/hooks', ['repo_name']);
235 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
235 pyroutes.register('edit_repo_caches', '/%(repo_name)s/settings/caches', ['repo_name']);
236 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
236 pyroutes.register('edit_repo_perms', '/%(repo_name)s/settings/permissions', ['repo_name']);
237 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
237 pyroutes.register('edit_repo_maintenance', '/%(repo_name)s/settings/maintenance', ['repo_name']);
238 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
238 pyroutes.register('edit_repo_maintenance_execute', '/%(repo_name)s/settings/maintenance/execute', ['repo_name']);
239 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
239 pyroutes.register('edit_repo_fields', '/%(repo_name)s/settings/fields', ['repo_name']);
240 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
240 pyroutes.register('edit_repo_fields_create', '/%(repo_name)s/settings/fields/create', ['repo_name']);
241 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
241 pyroutes.register('edit_repo_fields_delete', '/%(repo_name)s/settings/fields/%(field_id)s/delete', ['repo_name', 'field_id']);
242 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
242 pyroutes.register('repo_edit_toggle_locking', '/%(repo_name)s/settings/toggle_locking', ['repo_name']);
243 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
243 pyroutes.register('edit_repo_remote', '/%(repo_name)s/settings/remote', ['repo_name']);
244 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
244 pyroutes.register('edit_repo_remote_pull', '/%(repo_name)s/settings/remote/pull', ['repo_name']);
245 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
245 pyroutes.register('edit_repo_statistics', '/%(repo_name)s/settings/statistics', ['repo_name']);
246 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
246 pyroutes.register('edit_repo_statistics_reset', '/%(repo_name)s/settings/statistics/update', ['repo_name']);
247 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
247 pyroutes.register('edit_repo_issuetracker', '/%(repo_name)s/settings/issue_trackers', ['repo_name']);
248 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
248 pyroutes.register('edit_repo_issuetracker_test', '/%(repo_name)s/settings/issue_trackers/test', ['repo_name']);
249 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
249 pyroutes.register('edit_repo_issuetracker_delete', '/%(repo_name)s/settings/issue_trackers/delete', ['repo_name']);
250 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
250 pyroutes.register('edit_repo_issuetracker_update', '/%(repo_name)s/settings/issue_trackers/update', ['repo_name']);
251 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
251 pyroutes.register('edit_repo_vcs', '/%(repo_name)s/settings/vcs', ['repo_name']);
252 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
252 pyroutes.register('edit_repo_vcs_update', '/%(repo_name)s/settings/vcs/update', ['repo_name']);
253 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
253 pyroutes.register('edit_repo_vcs_svn_pattern_delete', '/%(repo_name)s/settings/vcs/svn_pattern/delete', ['repo_name']);
254 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
254 pyroutes.register('repo_reviewers', '/%(repo_name)s/settings/review/rules', ['repo_name']);
255 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
255 pyroutes.register('repo_default_reviewers_data', '/%(repo_name)s/settings/review/default-reviewers', ['repo_name']);
256 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
256 pyroutes.register('edit_repo_strip', '/%(repo_name)s/settings/strip', ['repo_name']);
257 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
257 pyroutes.register('strip_check', '/%(repo_name)s/settings/strip_check', ['repo_name']);
258 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
258 pyroutes.register('strip_execute', '/%(repo_name)s/settings/strip_execute', ['repo_name']);
259 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
259 pyroutes.register('edit_repo_audit_logs', '/%(repo_name)s/settings/audit_logs', ['repo_name']);
260 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
260 pyroutes.register('rss_feed_home', '/%(repo_name)s/feed/rss', ['repo_name']);
261 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
261 pyroutes.register('atom_feed_home', '/%(repo_name)s/feed/atom', ['repo_name']);
262 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
262 pyroutes.register('repo_summary', '/%(repo_name)s', ['repo_name']);
263 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
263 pyroutes.register('repo_summary_slash', '/%(repo_name)s/', ['repo_name']);
264 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
264 pyroutes.register('edit_repo_group', '/%(repo_group_name)s/_edit', ['repo_group_name']);
265 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
265 pyroutes.register('edit_repo_group_advanced', '/%(repo_group_name)s/_settings/advanced', ['repo_group_name']);
266 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
266 pyroutes.register('edit_repo_group_advanced_delete', '/%(repo_group_name)s/_settings/advanced/delete', ['repo_group_name']);
267 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
267 pyroutes.register('edit_repo_group_perms', '/%(repo_group_name)s/_settings/permissions', ['repo_group_name']);
268 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
268 pyroutes.register('edit_repo_group_perms_update', '/%(repo_group_name)s/_settings/permissions/update', ['repo_group_name']);
269 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
269 pyroutes.register('repo_group_home', '/%(repo_group_name)s', ['repo_group_name']);
270 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
270 pyroutes.register('repo_group_home_slash', '/%(repo_group_name)s/', ['repo_group_name']);
271 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
271 pyroutes.register('user_group_members_data', '/_admin/user_groups/%(user_group_id)s/members', ['user_group_id']);
272 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
272 pyroutes.register('edit_user_group_perms_summary', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary', ['user_group_id']);
273 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
273 pyroutes.register('edit_user_group_perms_summary_json', '/_admin/user_groups/%(user_group_id)s/edit/permissions_summary/json', ['user_group_id']);
274 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
274 pyroutes.register('edit_user_group', '/_admin/user_groups/%(user_group_id)s/edit', ['user_group_id']);
275 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
275 pyroutes.register('user_groups_update', '/_admin/user_groups/%(user_group_id)s/update', ['user_group_id']);
276 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
276 pyroutes.register('edit_user_group_global_perms', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions', ['user_group_id']);
277 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
277 pyroutes.register('edit_user_group_global_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/global_permissions/update', ['user_group_id']);
278 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
278 pyroutes.register('edit_user_group_perms', '/_admin/user_groups/%(user_group_id)s/edit/permissions', ['user_group_id']);
279 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
279 pyroutes.register('edit_user_group_perms_update', '/_admin/user_groups/%(user_group_id)s/edit/permissions/update', ['user_group_id']);
280 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
280 pyroutes.register('edit_user_group_advanced', '/_admin/user_groups/%(user_group_id)s/edit/advanced', ['user_group_id']);
281 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
281 pyroutes.register('edit_user_group_advanced_sync', '/_admin/user_groups/%(user_group_id)s/edit/advanced/sync', ['user_group_id']);
282 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
282 pyroutes.register('user_groups_delete', '/_admin/user_groups/%(user_group_id)s/delete', ['user_group_id']);
283 pyroutes.register('search', '/_admin/search', []);
283 pyroutes.register('search', '/_admin/search', []);
284 pyroutes.register('search_repo', '/%(repo_name)s/search', ['repo_name']);
284 pyroutes.register('search_repo', '/%(repo_name)s/_search', ['repo_name']);
285 pyroutes.register('search_repo_alt', '/%(repo_name)s/search', ['repo_name']);
286 pyroutes.register('search_repo_group', '/%(repo_group_name)s/_search', ['repo_group_name']);
285 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
287 pyroutes.register('user_profile', '/_profiles/%(username)s', ['username']);
286 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
288 pyroutes.register('user_group_profile', '/_profile_user_group/%(user_group_name)s', ['user_group_name']);
287 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
289 pyroutes.register('my_account_profile', '/_admin/my_account/profile', []);
288 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
290 pyroutes.register('my_account_edit', '/_admin/my_account/edit', []);
289 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
291 pyroutes.register('my_account_update', '/_admin/my_account/update', []);
290 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
292 pyroutes.register('my_account_password', '/_admin/my_account/password', []);
291 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
293 pyroutes.register('my_account_password_update', '/_admin/my_account/password/update', []);
292 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
294 pyroutes.register('my_account_auth_tokens_delete', '/_admin/my_account/auth_tokens/delete', []);
293 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
295 pyroutes.register('my_account_ssh_keys', '/_admin/my_account/ssh_keys', []);
294 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
296 pyroutes.register('my_account_ssh_keys_generate', '/_admin/my_account/ssh_keys/generate', []);
295 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
297 pyroutes.register('my_account_ssh_keys_add', '/_admin/my_account/ssh_keys/new', []);
296 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
298 pyroutes.register('my_account_ssh_keys_delete', '/_admin/my_account/ssh_keys/delete', []);
297 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
299 pyroutes.register('my_account_user_group_membership', '/_admin/my_account/user_group_membership', []);
298 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
300 pyroutes.register('my_account_emails', '/_admin/my_account/emails', []);
299 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
301 pyroutes.register('my_account_emails_add', '/_admin/my_account/emails/new', []);
300 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
302 pyroutes.register('my_account_emails_delete', '/_admin/my_account/emails/delete', []);
301 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
303 pyroutes.register('my_account_repos', '/_admin/my_account/repos', []);
302 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
304 pyroutes.register('my_account_watched', '/_admin/my_account/watched', []);
303 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
305 pyroutes.register('my_account_bookmarks', '/_admin/my_account/bookmarks', []);
304 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
306 pyroutes.register('my_account_bookmarks_update', '/_admin/my_account/bookmarks/update', []);
305 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
307 pyroutes.register('my_account_goto_bookmark', '/_admin/my_account/bookmark/%(bookmark_id)s', ['bookmark_id']);
306 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
308 pyroutes.register('my_account_perms', '/_admin/my_account/perms', []);
307 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
309 pyroutes.register('my_account_notifications', '/_admin/my_account/notifications', []);
308 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
310 pyroutes.register('my_account_notifications_toggle_visibility', '/_admin/my_account/toggle_visibility', []);
309 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
311 pyroutes.register('my_account_pullrequests', '/_admin/my_account/pull_requests', []);
310 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
312 pyroutes.register('my_account_pullrequests_data', '/_admin/my_account/pull_requests/data', []);
311 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
313 pyroutes.register('notifications_show_all', '/_admin/notifications', []);
312 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
314 pyroutes.register('notifications_mark_all_read', '/_admin/notifications/mark_all_read', []);
313 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
315 pyroutes.register('notifications_show', '/_admin/notifications/%(notification_id)s', ['notification_id']);
314 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
316 pyroutes.register('notifications_update', '/_admin/notifications/%(notification_id)s/update', ['notification_id']);
315 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
317 pyroutes.register('notifications_delete', '/_admin/notifications/%(notification_id)s/delete', ['notification_id']);
316 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
318 pyroutes.register('my_account_notifications_test_channelstream', '/_admin/my_account/test_channelstream', []);
317 pyroutes.register('gists_show', '/_admin/gists', []);
319 pyroutes.register('gists_show', '/_admin/gists', []);
318 pyroutes.register('gists_new', '/_admin/gists/new', []);
320 pyroutes.register('gists_new', '/_admin/gists/new', []);
319 pyroutes.register('gists_create', '/_admin/gists/create', []);
321 pyroutes.register('gists_create', '/_admin/gists/create', []);
320 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
322 pyroutes.register('gist_show', '/_admin/gists/%(gist_id)s', ['gist_id']);
321 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
323 pyroutes.register('gist_delete', '/_admin/gists/%(gist_id)s/delete', ['gist_id']);
322 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
324 pyroutes.register('gist_edit', '/_admin/gists/%(gist_id)s/edit', ['gist_id']);
323 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
325 pyroutes.register('gist_edit_check_revision', '/_admin/gists/%(gist_id)s/edit/check_revision', ['gist_id']);
324 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
326 pyroutes.register('gist_update', '/_admin/gists/%(gist_id)s/update', ['gist_id']);
325 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
327 pyroutes.register('gist_show_rev', '/_admin/gists/%(gist_id)s/%(revision)s', ['gist_id', 'revision']);
326 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
328 pyroutes.register('gist_show_formatted', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s', ['gist_id', 'revision', 'format']);
327 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
329 pyroutes.register('gist_show_formatted_path', '/_admin/gists/%(gist_id)s/%(revision)s/%(format)s/%(f_path)s', ['gist_id', 'revision', 'format', 'f_path']);
328 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
330 pyroutes.register('debug_style_home', '/_admin/debug_style', []);
329 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
331 pyroutes.register('debug_style_template', '/_admin/debug_style/t/%(t_path)s', ['t_path']);
330 pyroutes.register('apiv2', '/_admin/api', []);
332 pyroutes.register('apiv2', '/_admin/api', []);
331 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
333 pyroutes.register('admin_settings_license', '/_admin/settings/license', []);
332 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
334 pyroutes.register('admin_settings_license_unlock', '/_admin/settings/license_unlock', []);
333 pyroutes.register('login', '/_admin/login', []);
335 pyroutes.register('login', '/_admin/login', []);
334 pyroutes.register('register', '/_admin/register', []);
336 pyroutes.register('register', '/_admin/register', []);
335 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
337 pyroutes.register('repo_reviewers_review_rule_new', '/%(repo_name)s/settings/review/rules/new', ['repo_name']);
336 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
338 pyroutes.register('repo_reviewers_review_rule_edit', '/%(repo_name)s/settings/review/rules/%(rule_id)s', ['repo_name', 'rule_id']);
337 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
339 pyroutes.register('repo_reviewers_review_rule_delete', '/%(repo_name)s/settings/review/rules/%(rule_id)s/delete', ['repo_name', 'rule_id']);
338 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
340 pyroutes.register('plugin_admin_chat', '/_admin/plugin_admin_chat/%(action)s', ['action']);
339 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
341 pyroutes.register('edit_user_auth_tokens', '/_admin/users/%(user_id)s/edit/auth_tokens', ['user_id']);
340 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
342 pyroutes.register('edit_user_auth_tokens_add', '/_admin/users/%(user_id)s/edit/auth_tokens/new', ['user_id']);
341 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
343 pyroutes.register('admin_settings_scheduler_show_tasks', '/_admin/settings/scheduler/_tasks', []);
342 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
344 pyroutes.register('admin_settings_scheduler_show_all', '/_admin/settings/scheduler', []);
343 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
345 pyroutes.register('admin_settings_scheduler_new', '/_admin/settings/scheduler/new', []);
344 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
346 pyroutes.register('admin_settings_scheduler_create', '/_admin/settings/scheduler/create', []);
345 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
347 pyroutes.register('admin_settings_scheduler_edit', '/_admin/settings/scheduler/%(schedule_id)s', ['schedule_id']);
346 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
348 pyroutes.register('admin_settings_scheduler_update', '/_admin/settings/scheduler/%(schedule_id)s/update', ['schedule_id']);
347 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
349 pyroutes.register('admin_settings_scheduler_delete', '/_admin/settings/scheduler/%(schedule_id)s/delete', ['schedule_id']);
348 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
350 pyroutes.register('admin_settings_scheduler_execute', '/_admin/settings/scheduler/%(schedule_id)s/execute', ['schedule_id']);
349 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
351 pyroutes.register('admin_settings_automation', '/_admin/settings/automation', []);
350 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
352 pyroutes.register('admin_settings_automation_update', '/_admin/settings/automation/%(entry_id)s/update', ['entry_id']);
351 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
353 pyroutes.register('admin_permissions_branch', '/_admin/permissions/branch', []);
352 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
354 pyroutes.register('admin_permissions_branch_update', '/_admin/permissions/branch/update', []);
353 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
355 pyroutes.register('my_account_auth_tokens', '/_admin/my_account/auth_tokens', []);
354 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
356 pyroutes.register('my_account_auth_tokens_add', '/_admin/my_account/auth_tokens/new', []);
355 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
357 pyroutes.register('my_account_external_identity', '/_admin/my_account/external-identity', []);
356 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
358 pyroutes.register('my_account_external_identity_delete', '/_admin/my_account/external-identity/delete', []);
357 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
359 pyroutes.register('repo_automation', '/%(repo_name)s/settings/automation', ['repo_name']);
358 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
360 pyroutes.register('repo_automation_update', '/%(repo_name)s/settings/automation/%(entry_id)s/update', ['repo_name', 'entry_id']);
359 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
361 pyroutes.register('edit_repo_remote_push', '/%(repo_name)s/settings/remote/push', ['repo_name']);
360 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
362 pyroutes.register('edit_repo_perms_branch', '/%(repo_name)s/settings/branch_permissions', ['repo_name']);
361 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
363 pyroutes.register('edit_repo_perms_branch_delete', '/%(repo_name)s/settings/branch_permissions/%(rule_id)s/delete', ['repo_name', 'rule_id']);
362 }
364 }
@@ -1,68 +1,60 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base.mako"/>
2 <%inherit file="base.mako"/>
3 <%namespace name="widgets" file="/widgets.mako"/>
3 <%namespace name="widgets" file="/widgets.mako"/>
4
4
5 <%def name="breadcrumbs_links()">
5 <%def name="breadcrumbs_links()">
6 %if c.repo:
6 %if c.repo:
7 ${h.link_to('Settings',h.route_path('edit_repo', repo_name=c.repo.repo_name))}
7 ${_('Settings')}
8 &raquo;
9 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_integrations_home', repo_name=c.repo.repo_name))}
10 %elif c.repo_group:
8 %elif c.repo_group:
11 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
9 ${_('Settings')}
12 &raquo;
13 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
14 &raquo;
15 ${h.link_to(c.repo_group.group_name,h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name))}
16 &raquo;
17 ${h.link_to(_('Integrations'),request.route_url(route_name='repo_group_integrations_home', repo_group_name=c.repo_group.group_name))}
18 %else:
10 %else:
19 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
11 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
20 &raquo;
12 &raquo;
21 ${h.link_to(_('Settings'),h.route_path('admin_settings'))}
13 ${h.link_to(_('Settings'),h.route_path('admin_settings'))}
22 &raquo;
14 &raquo;
23 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
15 ${h.link_to(_('Integrations'),request.route_url(route_name='global_integrations_home'))}
24 %endif
16 %endif
25 &raquo;
17 &raquo;
26 ${_('Create new integration')}
18 ${_('Create new integration')}
27 </%def>
19 </%def>
28 <%widgets:panel class_='integrations'>
20 <%widgets:panel class_='integrations'>
29 <%def name="title()">
21 <%def name="title()">
30 %if c.repo:
22 %if c.repo:
31 ${_('Create New Integration for repository: {repo_name}').format(repo_name=c.repo.repo_name)}
23 ${_('Create New Integration for repository: {repo_name}').format(repo_name=c.repo.repo_name)}
32 %elif c.repo_group:
24 %elif c.repo_group:
33 ${_('Create New Integration for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
25 ${_('Create New Integration for repository group: {repo_group_name}').format(repo_group_name=c.repo_group.group_name)}
34 %else:
26 %else:
35 ${_('Create New Global Integration')}
27 ${_('Create New Global Integration')}
36 %endif
28 %endif
37 </%def>
29 </%def>
38
30
39 %for integration, IntegrationObject in c.available_integrations.items():
31 %for integration, IntegrationObject in c.available_integrations.items():
40 <%
32 <%
41 if c.repo:
33 if c.repo:
42 create_url = request.route_path('repo_integrations_create',
34 create_url = request.route_path('repo_integrations_create',
43 repo_name=c.repo.repo_name,
35 repo_name=c.repo.repo_name,
44 integration=integration)
36 integration=integration)
45 elif c.repo_group:
37 elif c.repo_group:
46 create_url = request.route_path('repo_group_integrations_create',
38 create_url = request.route_path('repo_group_integrations_create',
47 repo_group_name=c.repo_group.group_name,
39 repo_group_name=c.repo_group.group_name,
48 integration=integration)
40 integration=integration)
49 else:
41 else:
50 create_url = request.route_path('global_integrations_create',
42 create_url = request.route_path('global_integrations_create',
51 integration=integration)
43 integration=integration)
52 if IntegrationObject.is_dummy:
44 if IntegrationObject.is_dummy:
53 create_url = request.current_route_path()
45 create_url = request.current_route_path()
54 %>
46 %>
55 <a href="${create_url}" class="integration-box ${'dummy-integration' if IntegrationObject.is_dummy else ''}">
47 <a href="${create_url}" class="integration-box ${'dummy-integration' if IntegrationObject.is_dummy else ''}">
56 <%widgets:panel>
48 <%widgets:panel>
57 <h2>
49 <h2>
58 <div class="integration-icon">
50 <div class="integration-icon">
59 ${IntegrationObject.icon()|n}
51 ${IntegrationObject.icon()|n}
60 </div>
52 </div>
61 ${IntegrationObject.display_name}
53 ${IntegrationObject.display_name}
62 </h2>
54 </h2>
63 ${IntegrationObject.description or _('No description available')}
55 ${IntegrationObject.description or _('No description available')}
64 </%widgets:panel>
56 </%widgets:panel>
65 </a>
57 </a>
66 %endfor
58 %endfor
67 <div style="clear:both"></div>
59 <div style="clear:both"></div>
68 </%widgets:panel>
60 </%widgets:panel>
@@ -1,61 +1,48 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('%s repository group settings') % c.repo_group.name}
5 ${_('%s repository group settings') % c.repo_group.name}
6 %if c.rhodecode_name:
6 %if c.rhodecode_name:
7 &middot; ${h.branding(c.rhodecode_name)}
7 &middot; ${h.branding(c.rhodecode_name)}
8 %endif
8 %endif
9 </%def>
9 </%def>
10
10
11 <%def name="breadcrumbs_links()">
11 <%def name="menu_bar_nav()">
12 ${h.link_to(_('Admin'),h.route_path('admin_home'))}
12 ${self.menu_items(active='admin')}
13 &raquo;
14 ${h.link_to(_('Repository Groups'),h.route_path('repo_groups'))}
15 %if c.repo_group.parent_group:
16 &raquo; ${h.link_to(c.repo_group.parent_group.name, h.route_path('repo_group_home', repo_group_name=c.repo_group.parent_group.group_name))}
17 %endif
18 &raquo; ${c.repo_group.name}
19 </%def>
13 </%def>
20
14
21 <%def name="breadcrumbs_side_links()">
15 <%def name="menu_bar_subnav()">
22 <ul class="links">
16 ${self.repo_group_menu(active='options')}
23 <li>
24 <a href="${h.route_path('repo_group_new', _query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-success">${_(u'Add Child Group')}</a>
25 </li>
26 </ul>
27 </%def>
28
29 <%def name="menu_bar_nav()">
30 ${self.menu_items(active='admin')}
31 </%def>
17 </%def>
32
18
33 <%def name="main_content()">
19 <%def name="main_content()">
34 <%include file="/admin/repo_groups/repo_group_edit_${c.active}.mako"/>
20 <%include file="/admin/repo_groups/repo_group_edit_${c.active}.mako"/>
35 </%def>
21 </%def>
36
22
37 <%def name="main()">
23 <%def name="main()">
24
38 <div class="box">
25 <div class="box">
39 <div class="title">
26 <div class="title">
40 ${self.breadcrumbs()}
27 ${self.repo_group_page_title(c.repo_group)}
41 ${self.breadcrumbs_side_links()}
42 </div>
28 </div>
43
29
44 <div class="sidebar-col-wrapper">
30 <div class="sidebar-col-wrapper">
45 ##main
31 ##main
46 <div class="sidebar">
32 <div class="sidebar">
47 <ul class="nav nav-pills nav-stacked">
33 <ul class="nav nav-pills nav-stacked">
48 <li class="${'active' if c.active=='settings' else ''}"><a href="${h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name)}">${_('Settings')}</a></li>
34 <li class="${'active' if c.active=='settings' else ''}"><a href="${h.route_path('edit_repo_group', repo_group_name=c.repo_group.group_name)}">${_('Settings')}</a></li>
49 <li class="${'active' if c.active=='permissions' else ''}"><a href="${h.route_path('edit_repo_group_perms', repo_group_name=c.repo_group.group_name)}">${_('Permissions')}</a></li>
35 <li class="${'active' if c.active=='permissions' else ''}"><a href="${h.route_path('edit_repo_group_perms', repo_group_name=c.repo_group.group_name)}">${_('Permissions')}</a></li>
50 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.route_path('edit_repo_group_advanced', repo_group_name=c.repo_group.group_name)}">${_('Advanced')}</a></li>
36 <li class="${'active' if c.active=='advanced' else ''}"><a href="${h.route_path('edit_repo_group_advanced', repo_group_name=c.repo_group.group_name)}">${_('Advanced')}</a></li>
51 <li class="${'active' if c.active=='integrations' else ''}"><a href="${h.route_path('repo_group_integrations_home', repo_group_name=c.repo_group.group_name)}">${_('Integrations')}</a></li>
37 <li class="${'active' if c.active=='integrations' else ''}"><a href="${h.route_path('repo_group_integrations_home', repo_group_name=c.repo_group.group_name)}">${_('Integrations')}</a></li>
52 </ul>
38 </ul>
53 </div>
39 </div>
54
40
55 <div class="main-content-full-width">
41 <div class="main-content-full-width">
56 ${self.main_content()}
42 ${self.main_content()}
57 </div>
43 </div>
58
44
59 </div>
45 </div>
60 </div>
46 </div>
47
61 </%def>
48 </%def>
@@ -1,108 +1,108 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ##
2 ##
3 ## See also repo_settings.html
3 ## See also repo_settings.html
4 ##
4 ##
5 <%inherit file="/base/base.mako"/>
5 <%inherit file="/base/base.mako"/>
6
6
7 <%def name="title()">
7 <%def name="title()">
8 ${_('%s repository settings') % c.rhodecode_db_repo.repo_name}
8 ${_('%s repository settings') % c.rhodecode_db_repo.repo_name}
9 %if c.rhodecode_name:
9 %if c.rhodecode_name:
10 &middot; ${h.branding(c.rhodecode_name)}
10 &middot; ${h.branding(c.rhodecode_name)}
11 %endif
11 %endif
12 </%def>
12 </%def>
13
13
14 <%def name="breadcrumbs_links()">
14 <%def name="breadcrumbs_links()">
15 ${_('Settings')}
15 ${_('Settings')}
16 </%def>
16 </%def>
17
17
18 <%def name="menu_bar_nav()">
18 <%def name="menu_bar_nav()">
19 ${self.menu_items(active='repositories')}
19 ${self.menu_items(active='repositories')}
20 </%def>
20 </%def>
21
21
22 <%def name="menu_bar_subnav()">
22 <%def name="menu_bar_subnav()">
23 ${self.repo_menu(active='options')}
23 ${self.repo_menu(active='options')}
24 </%def>
24 </%def>
25
25
26 <%def name="main_content()">
26 <%def name="main_content()">
27 % if hasattr(c, 'repo_edit_template'):
27 % if hasattr(c, 'repo_edit_template'):
28 <%include file="${c.repo_edit_template}"/>
28 <%include file="${c.repo_edit_template}"/>
29 % else:
29 % else:
30 <%include file="/admin/repos/repo_edit_${c.active}.mako"/>
30 <%include file="/admin/repos/repo_edit_${c.active}.mako"/>
31 % endif
31 % endif
32 </%def>
32 </%def>
33
33
34
34
35 <%def name="main()">
35 <%def name="main()">
36 <div class="box">
36 <div class="box">
37 <div class="title">
37 <div class="title">
38 ${self.repo_page_title(c.rhodecode_db_repo)}
38 ${self.repo_page_title(c.rhodecode_db_repo)}
39 ${self.breadcrumbs()}
39
40 </div>
40 </div>
41
41
42 <div class="sidebar-col-wrapper scw-small">
42 <div class="sidebar-col-wrapper scw-small">
43 <div class="sidebar">
43 <div class="sidebar">
44 <ul class="nav nav-pills nav-stacked">
44 <ul class="nav nav-pills nav-stacked">
45 <li class="${'active' if c.active=='settings' else ''}">
45 <li class="${'active' if c.active=='settings' else ''}">
46 <a href="${h.route_path('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
46 <a href="${h.route_path('edit_repo', repo_name=c.repo_name)}">${_('Settings')}</a>
47 </li>
47 </li>
48 <li class="${'active' if c.active=='permissions' else ''}">
48 <li class="${'active' if c.active=='permissions' else ''}">
49 <a href="${h.route_path('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
49 <a href="${h.route_path('edit_repo_perms', repo_name=c.repo_name)}">${_('Permissions')}</a>
50 </li>
50 </li>
51 <li class="${'active' if c.active=='permissions_branch' else ''}">
51 <li class="${'active' if c.active=='permissions_branch' else ''}">
52 <a href="${h.route_path('edit_repo_perms_branch', repo_name=c.repo_name)}">${_('Branch Permissions')}</a>
52 <a href="${h.route_path('edit_repo_perms_branch', repo_name=c.repo_name)}">${_('Branch Permissions')}</a>
53 </li>
53 </li>
54 <li class="${'active' if c.active=='advanced' else ''}">
54 <li class="${'active' if c.active=='advanced' else ''}">
55 <a href="${h.route_path('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
55 <a href="${h.route_path('edit_repo_advanced', repo_name=c.repo_name)}">${_('Advanced')}</a>
56 </li>
56 </li>
57 <li class="${'active' if c.active=='vcs' else ''}">
57 <li class="${'active' if c.active=='vcs' else ''}">
58 <a href="${h.route_path('edit_repo_vcs', repo_name=c.repo_name)}">${_('VCS')}</a>
58 <a href="${h.route_path('edit_repo_vcs', repo_name=c.repo_name)}">${_('VCS')}</a>
59 </li>
59 </li>
60 <li class="${'active' if c.active=='fields' else ''}">
60 <li class="${'active' if c.active=='fields' else ''}">
61 <a href="${h.route_path('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra Fields')}</a>
61 <a href="${h.route_path('edit_repo_fields', repo_name=c.repo_name)}">${_('Extra Fields')}</a>
62 </li>
62 </li>
63 <li class="${'active' if c.active=='issuetracker' else ''}">
63 <li class="${'active' if c.active=='issuetracker' else ''}">
64 <a href="${h.route_path('edit_repo_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
64 <a href="${h.route_path('edit_repo_issuetracker', repo_name=c.repo_name)}">${_('Issue Tracker')}</a>
65 </li>
65 </li>
66 <li class="${'active' if c.active=='caches' else ''}">
66 <li class="${'active' if c.active=='caches' else ''}">
67 <a href="${h.route_path('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
67 <a href="${h.route_path('edit_repo_caches', repo_name=c.repo_name)}">${_('Caches')}</a>
68 </li>
68 </li>
69 %if c.rhodecode_db_repo.repo_type != 'svn':
69 %if c.rhodecode_db_repo.repo_type != 'svn':
70 <li class="${'active' if c.active=='remote' else ''}">
70 <li class="${'active' if c.active=='remote' else ''}">
71 <a href="${h.route_path('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote sync')}</a>
71 <a href="${h.route_path('edit_repo_remote', repo_name=c.repo_name)}">${_('Remote sync')}</a>
72 </li>
72 </li>
73 %endif
73 %endif
74 <li class="${'active' if c.active=='statistics' else ''}">
74 <li class="${'active' if c.active=='statistics' else ''}">
75 <a href="${h.route_path('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
75 <a href="${h.route_path('edit_repo_statistics', repo_name=c.repo_name)}">${_('Statistics')}</a>
76 </li>
76 </li>
77 <li class="${'active' if c.active=='integrations' else ''}">
77 <li class="${'active' if c.active=='integrations' else ''}">
78 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
78 <a href="${h.route_path('repo_integrations_home', repo_name=c.repo_name)}">${_('Integrations')}</a>
79 </li>
79 </li>
80 %if c.rhodecode_db_repo.repo_type != 'svn':
80 %if c.rhodecode_db_repo.repo_type != 'svn':
81 <li class="${'active' if c.active=='reviewers' else ''}">
81 <li class="${'active' if c.active=='reviewers' else ''}">
82 <a href="${h.route_path('repo_reviewers', repo_name=c.repo_name)}">${_('Reviewer Rules')}</a>
82 <a href="${h.route_path('repo_reviewers', repo_name=c.repo_name)}">${_('Reviewer Rules')}</a>
83 </li>
83 </li>
84 %endif
84 %endif
85 <li class="${'active' if c.active=='automation' else ''}">
85 <li class="${'active' if c.active=='automation' else ''}">
86 <a href="${h.route_path('repo_automation', repo_name=c.repo_name)}">${_('Automation')}</a>
86 <a href="${h.route_path('repo_automation', repo_name=c.repo_name)}">${_('Automation')}</a>
87 </li>
87 </li>
88 <li class="${'active' if c.active=='maintenance' else ''}">
88 <li class="${'active' if c.active=='maintenance' else ''}">
89 <a href="${h.route_path('edit_repo_maintenance', repo_name=c.repo_name)}">${_('Maintenance')}</a>
89 <a href="${h.route_path('edit_repo_maintenance', repo_name=c.repo_name)}">${_('Maintenance')}</a>
90 </li>
90 </li>
91 <li class="${'active' if c.active=='strip' else ''}">
91 <li class="${'active' if c.active=='strip' else ''}">
92 <a href="${h.route_path('edit_repo_strip', repo_name=c.repo_name)}">${_('Strip')}</a>
92 <a href="${h.route_path('edit_repo_strip', repo_name=c.repo_name)}">${_('Strip')}</a>
93 </li>
93 </li>
94 <li class="${'active' if c.active=='audit' else ''}">
94 <li class="${'active' if c.active=='audit' else ''}">
95 <a href="${h.route_path('edit_repo_audit_logs', repo_name=c.repo_name)}">${_('Audit logs')}</a>
95 <a href="${h.route_path('edit_repo_audit_logs', repo_name=c.repo_name)}">${_('Audit logs')}</a>
96 </li>
96 </li>
97
97
98 </ul>
98 </ul>
99 </div>
99 </div>
100
100
101 <div class="main-content-full-width">
101 <div class="main-content-full-width">
102 ${self.main_content()}
102 ${self.main_content()}
103 </div>
103 </div>
104
104
105 </div>
105 </div>
106 </div>
106 </div>
107
107
108 </%def> No newline at end of file
108 </%def>
@@ -1,729 +1,796 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.mako"/>
2 <%inherit file="root.mako"/>
3
3
4 <%include file="/ejs_templates/templates.html"/>
4 <%include file="/ejs_templates/templates.html"/>
5
5
6 <div class="outerwrapper">
6 <div class="outerwrapper">
7 <!-- HEADER -->
7 <!-- HEADER -->
8 <div class="header">
8 <div class="header">
9 <div id="header-inner" class="wrapper">
9 <div id="header-inner" class="wrapper">
10 <div id="logo">
10 <div id="logo">
11 <div class="logo-wrapper">
11 <div class="logo-wrapper">
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
12 <a href="${h.route_path('home')}"><img src="${h.asset('images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
13 </div>
13 </div>
14 %if c.rhodecode_name:
14 %if c.rhodecode_name:
15 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
15 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
16 %endif
16 %endif
17 </div>
17 </div>
18 <!-- MENU BAR NAV -->
18 <!-- MENU BAR NAV -->
19 ${self.menu_bar_nav()}
19 ${self.menu_bar_nav()}
20 <!-- END MENU BAR NAV -->
20 <!-- END MENU BAR NAV -->
21 </div>
21 </div>
22 </div>
22 </div>
23 ${self.menu_bar_subnav()}
23 ${self.menu_bar_subnav()}
24 <!-- END HEADER -->
24 <!-- END HEADER -->
25
25
26 <!-- CONTENT -->
26 <!-- CONTENT -->
27 <div id="content" class="wrapper">
27 <div id="content" class="wrapper">
28
28
29 <rhodecode-toast id="notifications"></rhodecode-toast>
29 <rhodecode-toast id="notifications"></rhodecode-toast>
30
30
31 <div class="main">
31 <div class="main">
32 ${next.main()}
32 ${next.main()}
33 </div>
33 </div>
34 </div>
34 </div>
35 <!-- END CONTENT -->
35 <!-- END CONTENT -->
36
36
37 </div>
37 </div>
38 <!-- FOOTER -->
38 <!-- FOOTER -->
39 <div id="footer">
39 <div id="footer">
40 <div id="footer-inner" class="title wrapper">
40 <div id="footer-inner" class="title wrapper">
41 <div>
41 <div>
42 <p class="footer-link-right">
42 <p class="footer-link-right">
43 % if c.visual.show_version:
43 % if c.visual.show_version:
44 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
44 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
45 % endif
45 % endif
46 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
46 &copy; 2010-${h.datetime.today().year}, <a href="${h.route_url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
47 % if c.visual.rhodecode_support_url:
47 % if c.visual.rhodecode_support_url:
48 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
48 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
49 % endif
49 % endif
50 </p>
50 </p>
51 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
51 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
52 <p class="server-instance" style="display:${sid}">
52 <p class="server-instance" style="display:${sid}">
53 ## display hidden instance ID if specially defined
53 ## display hidden instance ID if specially defined
54 % if c.rhodecode_instanceid:
54 % if c.rhodecode_instanceid:
55 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
55 ${_('RhodeCode instance id: {}').format(c.rhodecode_instanceid)}
56 % endif
56 % endif
57 </p>
57 </p>
58 </div>
58 </div>
59 </div>
59 </div>
60 </div>
60 </div>
61
61
62 <!-- END FOOTER -->
62 <!-- END FOOTER -->
63
63
64 ### MAKO DEFS ###
64 ### MAKO DEFS ###
65
65
66 <%def name="menu_bar_subnav()">
66 <%def name="menu_bar_subnav()">
67 </%def>
67 </%def>
68
68
69 <%def name="breadcrumbs(class_='breadcrumbs')">
69 <%def name="breadcrumbs(class_='breadcrumbs')">
70 <div class="${class_}">
70 <div class="${class_}">
71 ${self.breadcrumbs_links()}
71 ${self.breadcrumbs_links()}
72 </div>
72 </div>
73 </%def>
73 </%def>
74
74
75 <%def name="admin_menu()">
75 <%def name="admin_menu()">
76 <ul class="admin_menu submenu">
76 <ul class="admin_menu submenu">
77 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
77 <li><a href="${h.route_path('admin_audit_logs')}">${_('Admin audit logs')}</a></li>
78 <li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
78 <li><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
79 <li><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
79 <li><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
80 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
80 <li><a href="${h.route_path('users')}">${_('Users')}</a></li>
81 <li><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
81 <li><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
82 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
82 <li><a href="${h.route_path('admin_permissions_application')}">${_('Permissions')}</a></li>
83 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
83 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
84 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
84 <li><a href="${h.route_path('global_integrations_home')}">${_('Integrations')}</a></li>
85 <li><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
85 <li><a href="${h.route_path('admin_defaults_repositories')}">${_('Defaults')}</a></li>
86 <li class="last"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
86 <li class="last"><a href="${h.route_path('admin_settings')}">${_('Settings')}</a></li>
87 </ul>
87 </ul>
88 </%def>
88 </%def>
89
89
90
90
91 <%def name="dt_info_panel(elements)">
91 <%def name="dt_info_panel(elements)">
92 <dl class="dl-horizontal">
92 <dl class="dl-horizontal">
93 %for dt, dd, title, show_items in elements:
93 %for dt, dd, title, show_items in elements:
94 <dt>${dt}:</dt>
94 <dt>${dt}:</dt>
95 <dd title="${h.tooltip(title)}">
95 <dd title="${h.tooltip(title)}">
96 %if callable(dd):
96 %if callable(dd):
97 ## allow lazy evaluation of elements
97 ## allow lazy evaluation of elements
98 ${dd()}
98 ${dd()}
99 %else:
99 %else:
100 ${dd}
100 ${dd}
101 %endif
101 %endif
102 %if show_items:
102 %if show_items:
103 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
103 <span class="btn-collapse" data-toggle="item-${h.md5_safe(dt)[:6]}-details">${_('Show More')} </span>
104 %endif
104 %endif
105 </dd>
105 </dd>
106
106
107 %if show_items:
107 %if show_items:
108 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
108 <div class="collapsable-content" data-toggle="item-${h.md5_safe(dt)[:6]}-details" style="display: none">
109 %for item in show_items:
109 %for item in show_items:
110 <dt></dt>
110 <dt></dt>
111 <dd>${item}</dd>
111 <dd>${item}</dd>
112 %endfor
112 %endfor
113 </div>
113 </div>
114 %endif
114 %endif
115
115
116 %endfor
116 %endfor
117 </dl>
117 </dl>
118 </%def>
118 </%def>
119
119
120
120
121 <%def name="gravatar(email, size=16)">
121 <%def name="gravatar(email, size=16)">
122 <%
122 <%
123 if (size > 16):
123 if (size > 16):
124 gravatar_class = 'gravatar gravatar-large'
124 gravatar_class = 'gravatar gravatar-large'
125 else:
125 else:
126 gravatar_class = 'gravatar'
126 gravatar_class = 'gravatar'
127 %>
127 %>
128 <%doc>
128 <%doc>
129 TODO: johbo: For now we serve double size images to make it smooth
129 TODO: johbo: For now we serve double size images to make it smooth
130 for retina. This is how it worked until now. Should be replaced
130 for retina. This is how it worked until now. Should be replaced
131 with a better solution at some point.
131 with a better solution at some point.
132 </%doc>
132 </%doc>
133 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
133 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
134 </%def>
134 </%def>
135
135
136
136
137 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
137 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
138 <% email = h.email_or_none(contact) %>
138 <% email = h.email_or_none(contact) %>
139 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
139 <div class="rc-user tooltip" title="${h.tooltip(h.author_string(email))}">
140 ${self.gravatar(email, size)}
140 ${self.gravatar(email, size)}
141 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
141 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
142 </div>
142 </div>
143 </%def>
143 </%def>
144
144
145
145
146 ## admin menu used for people that have some admin resources
146 ## admin menu used for people that have some admin resources
147 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
147 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
148 <ul class="submenu">
148 <ul class="submenu">
149 %if repositories:
149 %if repositories:
150 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
150 <li class="local-admin-repos"><a href="${h.route_path('repos')}">${_('Repositories')}</a></li>
151 %endif
151 %endif
152 %if repository_groups:
152 %if repository_groups:
153 <li class="local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
153 <li class="local-admin-repo-groups"><a href="${h.route_path('repo_groups')}">${_('Repository groups')}</a></li>
154 %endif
154 %endif
155 %if user_groups:
155 %if user_groups:
156 <li class="local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
156 <li class="local-admin-user-groups"><a href="${h.route_path('user_groups')}">${_('User groups')}</a></li>
157 %endif
157 %endif
158 </ul>
158 </ul>
159 </%def>
159 </%def>
160
160
161 <%def name="repo_page_title(repo_instance)">
161 <%def name="repo_page_title(repo_instance)">
162 <div class="title-content">
162 <div class="title-content">
163 <div class="title-main">
163 <div class="title-main">
164 ## SVN/HG/GIT icons
164 ## SVN/HG/GIT icons
165 %if h.is_hg(repo_instance):
165 %if h.is_hg(repo_instance):
166 <i class="icon-hg"></i>
166 <i class="icon-hg"></i>
167 %endif
167 %endif
168 %if h.is_git(repo_instance):
168 %if h.is_git(repo_instance):
169 <i class="icon-git"></i>
169 <i class="icon-git"></i>
170 %endif
170 %endif
171 %if h.is_svn(repo_instance):
171 %if h.is_svn(repo_instance):
172 <i class="icon-svn"></i>
172 <i class="icon-svn"></i>
173 %endif
173 %endif
174
174
175 ## public/private
175 ## public/private
176 %if repo_instance.private:
176 %if repo_instance.private:
177 <i class="icon-repo-private"></i>
177 <i class="icon-repo-private"></i>
178 %else:
178 %else:
179 <i class="icon-repo-public"></i>
179 <i class="icon-repo-public"></i>
180 %endif
180 %endif
181
181
182 ## repo name with group name
182 ## repo name with group name
183 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
183 ${h.breadcrumb_repo_link(repo_instance)}
184
184
185 </div>
185 </div>
186
186
187 ## FORKED
187 ## FORKED
188 %if repo_instance.fork:
188 %if repo_instance.fork:
189 <p>
189 <p>
190 <i class="icon-code-fork"></i> ${_('Fork of')}
190 <i class="icon-code-fork"></i> ${_('Fork of')}
191 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
191 ${h.link_to_if(c.has_origin_repo_read_perm,repo_instance.fork.repo_name, h.route_path('repo_summary', repo_name=repo_instance.fork.repo_name))}
192 </p>
192 </p>
193 %endif
193 %endif
194
194
195 ## IMPORTED FROM REMOTE
195 ## IMPORTED FROM REMOTE
196 %if repo_instance.clone_uri:
196 %if repo_instance.clone_uri:
197 <p>
197 <p>
198 <i class="icon-code-fork"></i> ${_('Clone from')}
198 <i class="icon-code-fork"></i> ${_('Clone from')}
199 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
199 <a href="${h.safe_str(h.hide_credentials(repo_instance.clone_uri))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
200 </p>
200 </p>
201 %endif
201 %endif
202
202
203 ## LOCKING STATUS
203 ## LOCKING STATUS
204 %if repo_instance.locked[0]:
204 %if repo_instance.locked[0]:
205 <p class="locking_locked">
205 <p class="locking_locked">
206 <i class="icon-repo-lock"></i>
206 <i class="icon-repo-lock"></i>
207 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
207 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
208 </p>
208 </p>
209 %elif repo_instance.enable_locking:
209 %elif repo_instance.enable_locking:
210 <p class="locking_unlocked">
210 <p class="locking_unlocked">
211 <i class="icon-repo-unlock"></i>
211 <i class="icon-repo-unlock"></i>
212 ${_('Repository not locked. Pull repository to lock it.')}
212 ${_('Repository not locked. Pull repository to lock it.')}
213 </p>
213 </p>
214 %endif
214 %endif
215
215
216 </div>
216 </div>
217 </%def>
217 </%def>
218
218
219 <%def name="repo_menu(active=None)">
219 <%def name="repo_menu(active=None)">
220 <%
220 <%
221 def is_active(selected):
221 def is_active(selected):
222 if selected == active:
222 if selected == active:
223 return "active"
223 return "active"
224 %>
224 %>
225
225
226 <!--- CONTEXT BAR -->
226 <!--- CONTEXT BAR -->
227 <div id="context-bar">
227 <div id="context-bar">
228 <div class="wrapper">
228 <div class="wrapper">
229 <ul id="context-pages" class="navigation horizontal-list">
229 <ul id="context-pages" class="navigation horizontal-list">
230 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
230 <li class="${is_active('summary')}"><a class="menulink" href="${h.route_path('repo_summary', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
231 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
231 <li class="${is_active('changelog')}"><a class="menulink" href="${h.route_path('repo_changelog', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
232 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
232 <li class="${is_active('files')}"><a class="menulink" href="${h.route_path('repo_files', repo_name=c.repo_name, commit_id=c.rhodecode_db_repo.landing_rev[1], f_path='')}"><div class="menulabel">${_('Files')}</div></a></li>
233 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
233 <li class="${is_active('compare')}"><a class="menulink" href="${h.route_path('repo_compare_select',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a></li>
234 <li class="${is_active('search')}"><a class="menulink" href="${h.route_path('search_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Search')}</div></a></li>
234 <li class="${is_active('search')}"><a class="menulink" href="${h.route_path('search_repo',repo_name=c.repo_name)}"><div class="menulabel">${_('Search')}</div></a></li>
235
235
236 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
236 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
237 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
237 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
238 <li class="${is_active('showpullrequest')}">
238 <li class="${is_active('showpullrequest')}">
239 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
239 <a class="menulink" href="${h.route_path('pullrequest_show_all', repo_name=c.repo_name)}" title="${h.tooltip(_('Show Pull Requests for %s') % c.repo_name)}">
240 %if c.repository_pull_requests:
240 %if c.repository_pull_requests:
241 <span class="pr_notifications">${c.repository_pull_requests}</span>
241 <span class="pr_notifications">${c.repository_pull_requests}</span>
242 %endif
242 %endif
243 <div class="menulabel">${_('Pull Requests')}</div>
243 <div class="menulabel">${_('Pull Requests')}</div>
244 </a>
244 </a>
245 </li>
245 </li>
246 %endif
246 %endif
247
247
248 <li class="${is_active('options')}">
248 <li class="${is_active('options')}">
249 <a class="menulink dropdown">
249 <a class="menulink dropdown">
250 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
250 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
251 </a>
251 </a>
252 <ul class="submenu">
252 <ul class="submenu">
253 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
253 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
254 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
254 <li><a href="${h.route_path('edit_repo',repo_name=c.repo_name)}">${_('Repository Settings')}</a></li>
255 %endif
255 %endif
256 %if c.rhodecode_db_repo.fork:
256 %if c.rhodecode_db_repo.fork:
257 <li>
257 <li>
258 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
258 <a title="${h.tooltip(_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name))}"
259 href="${h.route_path('repo_compare',
259 href="${h.route_path('repo_compare',
260 repo_name=c.rhodecode_db_repo.fork.repo_name,
260 repo_name=c.rhodecode_db_repo.fork.repo_name,
261 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
261 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
262 source_ref=c.rhodecode_db_repo.landing_rev[1],
262 source_ref=c.rhodecode_db_repo.landing_rev[1],
263 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
263 target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
264 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
264 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
265 _query=dict(merge=1))}"
265 _query=dict(merge=1))}"
266 >
266 >
267 ${_('Compare fork')}
267 ${_('Compare fork')}
268 </a>
268 </a>
269 </li>
269 </li>
270 %endif
270 %endif
271
271
272 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
272 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
273 %if c.rhodecode_db_repo.locked[0]:
273 %if c.rhodecode_db_repo.locked[0]:
274 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
274 <li><a class="locking_del" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
275 %else:
275 %else:
276 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
276 <li><a class="locking_add" href="${h.route_path('repo_edit_toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
277 %endif
277 %endif
278 %endif
278 %endif
279 %if c.rhodecode_user.username != h.DEFAULT_USER:
279 %if c.rhodecode_user.username != h.DEFAULT_USER:
280 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
280 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
281 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
281 <li><a href="${h.route_path('repo_fork_new',repo_name=c.repo_name)}">${_('Fork')}</a></li>
282 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
282 <li><a href="${h.route_path('pullrequest_new',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
283 %endif
283 %endif
284 %endif
284 %endif
285 </ul>
285 </ul>
286 </li>
286 </li>
287 </ul>
287 </ul>
288 </div>
288 </div>
289 <div class="clear"></div>
289 <div class="clear"></div>
290 </div>
290 </div>
291 % if c.rhodecode_db_repo.archived:
291 % if c.rhodecode_db_repo.archived:
292 <div class="alert alert-warning text-center">
292 <div class="alert alert-warning text-center">
293 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
293 <strong>${_('This repository has been archived. It is now read-only.')}</strong>
294 </div>
294 </div>
295 % endif
295 % endif
296 <!--- END CONTEXT BAR -->
296 <!--- END CONTEXT BAR -->
297
297
298 </%def>
298 </%def>
299
299
300 <%def name="repo_group_page_title(repo_group_instance)">
301 <div class="title-content">
302 <div class="title-main">
303 ## Repository Group icon
304 <i class="icon-folder-close"></i>
305
306 ## repo name with group name
307 ${h.breadcrumb_repo_group_link(repo_group_instance)}
308 </div>
309
310 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
311 <div class="repo-group-desc">
312 ${dt.repo_group_desc(repo_group_instance.description_safe, repo_group_instance.personal, c.visual.stylify_metatags)}
313 </div>
314
315 </div>
316 </%def>
317
318 <%def name="repo_group_menu(active=None)">
319 <%
320 def is_active(selected):
321 if selected == active:
322 return "active"
323
324 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
325
326 gr_name = c.repo_group.group_name if c.repo_group else None
327 # create repositories with write permission on group is set to true
328 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
329 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
330 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
331
332 %>
333
334 <!--- CONTEXT BAR -->
335 <div id="context-bar">
336 <div class="wrapper">
337 <ul id="context-pages" class="navigation horizontal-list">
338 <li class="${is_active('home')}"><a class="menulink" href="${h.route_path('repo_group_home', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Group Home')}</div></a></li>
339 <li class="${is_active('search')}"><a class="menulink" href="${h.route_path('search_repo_group', repo_group_name=c.repo_group.group_name)}"><div class="menulabel">${_('Search')}</div></a></li>
340
341 <li class="${is_active('options')}">
342 <a class="menulink dropdown">
343 <div class="menulabel">${_('Options')} <div class="show_more"></div></div>
344 </a>
345 <ul class="submenu">
346 %if is_admin or group_admin:
347 <li><a href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}">${_('Group Settings')}</a></li>
348 %endif
349 %if is_admin or group_admin or (group_write and create_on_write):
350 <li><a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}">${_('Add Repository')}</a></li>
351 %endif
352 %if is_admin or group_admin:
353 <li><a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}">${_(u'Add Parent Group')}</a></li>
354 %endif
355 </ul>
356 </li>
357 </ul>
358 </div>
359 <div class="clear"></div>
360 </div>
361
362 <!--- END CONTEXT BAR -->
363
364 </%def>
365
366
300 <%def name="usermenu(active=False)">
367 <%def name="usermenu(active=False)">
301 ## USER MENU
368 ## USER MENU
302 <li id="quick_login_li" class="${'active' if active else ''}">
369 <li id="quick_login_li" class="${'active' if active else ''}">
303 % if c.rhodecode_user.username == h.DEFAULT_USER:
370 % if c.rhodecode_user.username == h.DEFAULT_USER:
304 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
371 <a id="quick_login_link" class="menulink childs" href="${h.route_path('login', _query={'came_from': h.current_route_path(request)})}">
305 ${gravatar(c.rhodecode_user.email, 20)}
372 ${gravatar(c.rhodecode_user.email, 20)}
306 <span class="user">
373 <span class="user">
307 <span>${_('Sign in')}</span>
374 <span>${_('Sign in')}</span>
308 </span>
375 </span>
309 </a>
376 </a>
310 % else:
377 % else:
311 ## logged in user
378 ## logged in user
312 <a id="quick_login_link" class="menulink childs">
379 <a id="quick_login_link" class="menulink childs">
313 ${gravatar(c.rhodecode_user.email, 20)}
380 ${gravatar(c.rhodecode_user.email, 20)}
314 <span class="user">
381 <span class="user">
315 <span class="menu_link_user">${c.rhodecode_user.username}</span>
382 <span class="menu_link_user">${c.rhodecode_user.username}</span>
316 <div class="show_more"></div>
383 <div class="show_more"></div>
317 </span>
384 </span>
318 </a>
385 </a>
319 ## subnav with menu for logged in user
386 ## subnav with menu for logged in user
320 <div class="user-menu submenu">
387 <div class="user-menu submenu">
321 <div id="quick_login">
388 <div id="quick_login">
322 %if c.rhodecode_user.username != h.DEFAULT_USER:
389 %if c.rhodecode_user.username != h.DEFAULT_USER:
323 <div class="">
390 <div class="">
324 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
391 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
325 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
392 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
326 <div class="email">${c.rhodecode_user.email}</div>
393 <div class="email">${c.rhodecode_user.email}</div>
327 </div>
394 </div>
328 <div class="">
395 <div class="">
329 <ol class="links">
396 <ol class="links">
330 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
397 <li>${h.link_to(_(u'My account'),h.route_path('my_account_profile'))}</li>
331 % if c.rhodecode_user.personal_repo_group:
398 % if c.rhodecode_user.personal_repo_group:
332 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
399 <li>${h.link_to(_(u'My personal group'), h.route_path('repo_group_home', repo_group_name=c.rhodecode_user.personal_repo_group.group_name))}</li>
333 % endif
400 % endif
334 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
401 <li>${h.link_to(_(u'Pull Requests'), h.route_path('my_account_pullrequests'))}</li>
335 ## bookmark-items
402 ## bookmark-items
336 <li class="bookmark-items">
403 <li class="bookmark-items">
337 ${_('Bookmarks')}
404 ${_('Bookmarks')}
338 <div class="pull-right">
405 <div class="pull-right">
339 <a href="${h.route_path('my_account_bookmarks')}">${_('Manage')}</a>
406 <a href="${h.route_path('my_account_bookmarks')}">${_('Manage')}</a>
340 </div>
407 </div>
341 </li>
408 </li>
342 % if not c.bookmark_items:
409 % if not c.bookmark_items:
343 <li>
410 <li>
344 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
411 <a href="${h.route_path('my_account_bookmarks')}">${_('No Bookmarks yet.')}</a>
345 </li>
412 </li>
346 % endif
413 % endif
347 % for item in c.bookmark_items:
414 % for item in c.bookmark_items:
348 <li>
415 <li>
349 % if item.repository:
416 % if item.repository:
350 <div>
417 <div>
351 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
418 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
352 <code>${item.position}</code>
419 <code>${item.position}</code>
353 % if item.repository.repo_type == 'hg':
420 % if item.repository.repo_type == 'hg':
354 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
421 <i class="icon-hg" title="${_('Repository')}" style="font-size: 16px"></i>
355 % elif item.repository.repo_type == 'git':
422 % elif item.repository.repo_type == 'git':
356 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
423 <i class="icon-git" title="${_('Repository')}" style="font-size: 16px"></i>
357 % elif item.repository.repo_type == 'svn':
424 % elif item.repository.repo_type == 'svn':
358 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
425 <i class="icon-svn" title="${_('Repository')}" style="font-size: 16px"></i>
359 % endif
426 % endif
360 ${(item.title or h.shorter(item.repository.repo_name, 30))}
427 ${(item.title or h.shorter(item.repository.repo_name, 30))}
361 </a>
428 </a>
362 </div>
429 </div>
363 % elif item.repository_group:
430 % elif item.repository_group:
364 <div>
431 <div>
365 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
432 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
366 <code>${item.position}</code>
433 <code>${item.position}</code>
367 <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i>
434 <i class="icon-folder-close" title="${_('Repository group')}" style="font-size: 16px"></i>
368 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
435 ${(item.title or h.shorter(item.repository_group.group_name, 30))}
369 </a>
436 </a>
370 </div>
437 </div>
371 % else:
438 % else:
372 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
439 <a class="bookmark-item" href="${h.route_path('my_account_goto_bookmark', bookmark_id=item.position)}">
373 <code>${item.position}</code>
440 <code>${item.position}</code>
374 ${item.title}
441 ${item.title}
375 </a>
442 </a>
376 % endif
443 % endif
377 </li>
444 </li>
378 % endfor
445 % endfor
379
446
380 <li class="logout">
447 <li class="logout">
381 ${h.secure_form(h.route_path('logout'), request=request)}
448 ${h.secure_form(h.route_path('logout'), request=request)}
382 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
449 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
383 ${h.end_form()}
450 ${h.end_form()}
384 </li>
451 </li>
385 </ol>
452 </ol>
386 </div>
453 </div>
387 %endif
454 %endif
388 </div>
455 </div>
389 </div>
456 </div>
390 ## unread counter
457 ## unread counter
391 <div class="pill_container">
458 <div class="pill_container">
392 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
459 <a class="menu_link_notifications ${'empty' if c.unread_notifications == 0 else ''}" href="${h.route_path('notifications_show_all')}">${c.unread_notifications}</a>
393 </div>
460 </div>
394 % endif
461 % endif
395 </li>
462 </li>
396 </%def>
463 </%def>
397
464
398 <%def name="menu_items(active=None)">
465 <%def name="menu_items(active=None)">
399 <%
466 <%
400 def is_active(selected):
467 def is_active(selected):
401 if selected == active:
468 if selected == active:
402 return "active"
469 return "active"
403 return ""
470 return ""
404 %>
471 %>
405
472
406 <ul id="quick" class="main_nav navigation horizontal-list">
473 <ul id="quick" class="main_nav navigation horizontal-list">
407 ## notice box for important system messages
474 ## notice box for important system messages
408 <li style="display: none">
475 <li style="display: none">
409 <a class="notice-box" href="#openNotice" onclick="showNoticeBox(); return false">
476 <a class="notice-box" href="#openNotice" onclick="showNoticeBox(); return false">
410 <div class="menulabel-notice" >
477 <div class="menulabel-notice" >
411 0
478 0
412 </div>
479 </div>
413 </a>
480 </a>
414 </li>
481 </li>
415
482
416 ## Main filter
483 ## Main filter
417 <li>
484 <li>
418 <div class="menulabel main_filter_box">
485 <div class="menulabel main_filter_box">
419 <div class="main_filter_input_box">
486 <div class="main_filter_input_box">
420 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value=""/>
487 <input class="main_filter_input" id="main_filter" size="15" type="text" name="main_filter" placeholder="${_('search / go to...')}" value=""/>
421 </div>
488 </div>
422 <div class="main_filter_help_box">
489 <div class="main_filter_help_box">
423 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
490 <a href="#showFilterHelp" onclick="showMainFilterBox(); return false">?</a>
424 </div>
491 </div>
425 </div>
492 </div>
426
493
427 <div id="main_filter_help" style="display: none">
494 <div id="main_filter_help" style="display: none">
428 Use '/' key to quickly access this field.
495 Use '/' key to quickly access this field.
429 Enter name of repository, or repository group for quick search.
496 Enter name of repository, or repository group for quick search.
430
497
431 Prefix query to allow special search:
498 Prefix query to allow special search:
432
499
433 user:admin, to search for usernames
500 user:admin, to search for usernames
434
501
435 user_group:devops, to search for user groups
502 user_group:devops, to search for user groups
436
503
437 commit:efced4, to search for commits
504 commit:efced4, to search for commits
438
505
439 </div>
506 </div>
440 </li>
507 </li>
441
508
442 ## ROOT MENU
509 ## ROOT MENU
443 %if c.rhodecode_user.username != h.DEFAULT_USER:
510 %if c.rhodecode_user.username != h.DEFAULT_USER:
444 <li class="${is_active('journal')}">
511 <li class="${is_active('journal')}">
445 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
512 <a class="menulink" title="${_('Show activity journal')}" href="${h.route_path('journal')}">
446 <div class="menulabel">${_('Journal')}</div>
513 <div class="menulabel">${_('Journal')}</div>
447 </a>
514 </a>
448 </li>
515 </li>
449 %else:
516 %else:
450 <li class="${is_active('journal')}">
517 <li class="${is_active('journal')}">
451 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
518 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.route_path('journal_public')}">
452 <div class="menulabel">${_('Public journal')}</div>
519 <div class="menulabel">${_('Public journal')}</div>
453 </a>
520 </a>
454 </li>
521 </li>
455 %endif
522 %endif
456 <li class="${is_active('gists')}">
523 <li class="${is_active('gists')}">
457 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
524 <a class="menulink childs" title="${_('Show Gists')}" href="${h.route_path('gists_show')}">
458 <div class="menulabel">${_('Gists')}</div>
525 <div class="menulabel">${_('Gists')}</div>
459 </a>
526 </a>
460 </li>
527 </li>
461 <li class="${is_active('search')}">
528 <li class="${is_active('search')}">
462 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
529 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.route_path('search')}">
463 <div class="menulabel">${_('Search')}</div>
530 <div class="menulabel">${_('Search')}</div>
464 </a>
531 </a>
465 </li>
532 </li>
466 % if h.HasPermissionAll('hg.admin')('access admin main page'):
533 % if h.HasPermissionAll('hg.admin')('access admin main page'):
467 <li class="${is_active('admin')}">
534 <li class="${is_active('admin')}">
468 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
535 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
469 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
536 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
470 </a>
537 </a>
471 ${admin_menu()}
538 ${admin_menu()}
472 </li>
539 </li>
473 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
540 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
474 <li class="${is_active('admin')}">
541 <li class="${is_active('admin')}">
475 <a class="menulink childs" title="${_('Delegated Admin settings')}">
542 <a class="menulink childs" title="${_('Delegated Admin settings')}">
476 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
543 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
477 </a>
544 </a>
478 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
545 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
479 c.rhodecode_user.repository_groups_admin,
546 c.rhodecode_user.repository_groups_admin,
480 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
547 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
481 </li>
548 </li>
482 % endif
549 % endif
483 ## render extra user menu
550 ## render extra user menu
484 ${usermenu(active=(active=='my_account'))}
551 ${usermenu(active=(active=='my_account'))}
485
552
486 % if c.debug_style:
553 % if c.debug_style:
487 <li>
554 <li>
488 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
555 <a class="menulink" title="${_('Style')}" href="${h.route_path('debug_style_home')}">
489 <div class="menulabel">${_('[Style]')}</div>
556 <div class="menulabel">${_('[Style]')}</div>
490 </a>
557 </a>
491 </li>
558 </li>
492 % endif
559 % endif
493 </ul>
560 </ul>
494
561
495 <script type="text/javascript">
562 <script type="text/javascript">
496 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
563 var visualShowPublicIcon = "${c.visual.show_public_icon}" == "True";
497
564
498 var formatRepoResult = function(result, container, query, escapeMarkup) {
565 var formatRepoResult = function(result, container, query, escapeMarkup) {
499 return function(data, escapeMarkup) {
566 return function(data, escapeMarkup) {
500 if (!data.repo_id){
567 if (!data.repo_id){
501 return data.text; // optgroup text Repositories
568 return data.text; // optgroup text Repositories
502 }
569 }
503
570
504 var tmpl = '';
571 var tmpl = '';
505 var repoType = data['repo_type'];
572 var repoType = data['repo_type'];
506 var repoName = data['text'];
573 var repoName = data['text'];
507
574
508 if(data && data.type == 'repo'){
575 if(data && data.type == 'repo'){
509 if(repoType === 'hg'){
576 if(repoType === 'hg'){
510 tmpl += '<i class="icon-hg"></i> ';
577 tmpl += '<i class="icon-hg"></i> ';
511 }
578 }
512 else if(repoType === 'git'){
579 else if(repoType === 'git'){
513 tmpl += '<i class="icon-git"></i> ';
580 tmpl += '<i class="icon-git"></i> ';
514 }
581 }
515 else if(repoType === 'svn'){
582 else if(repoType === 'svn'){
516 tmpl += '<i class="icon-svn"></i> ';
583 tmpl += '<i class="icon-svn"></i> ';
517 }
584 }
518 if(data['private']){
585 if(data['private']){
519 tmpl += '<i class="icon-lock" ></i> ';
586 tmpl += '<i class="icon-lock" ></i> ';
520 }
587 }
521 else if(visualShowPublicIcon){
588 else if(visualShowPublicIcon){
522 tmpl += '<i class="icon-unlock-alt"></i> ';
589 tmpl += '<i class="icon-unlock-alt"></i> ';
523 }
590 }
524 }
591 }
525 tmpl += escapeMarkup(repoName);
592 tmpl += escapeMarkup(repoName);
526 return tmpl;
593 return tmpl;
527
594
528 }(result, escapeMarkup);
595 }(result, escapeMarkup);
529 };
596 };
530
597
531 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
598 var formatRepoGroupResult = function(result, container, query, escapeMarkup) {
532 return function(data, escapeMarkup) {
599 return function(data, escapeMarkup) {
533 if (!data.repo_group_id){
600 if (!data.repo_group_id){
534 return data.text; // optgroup text Repositories
601 return data.text; // optgroup text Repositories
535 }
602 }
536
603
537 var tmpl = '';
604 var tmpl = '';
538 var repoGroupName = data['text'];
605 var repoGroupName = data['text'];
539
606
540 if(data){
607 if(data){
541
608
542 tmpl += '<i class="icon-folder-close"></i> ';
609 tmpl += '<i class="icon-folder-close"></i> ';
543
610
544 }
611 }
545 tmpl += escapeMarkup(repoGroupName);
612 tmpl += escapeMarkup(repoGroupName);
546 return tmpl;
613 return tmpl;
547
614
548 }(result, escapeMarkup);
615 }(result, escapeMarkup);
549 };
616 };
550
617
551
618
552 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
619 var autocompleteMainFilterFormatResult = function (data, value, org_formatter) {
553
620
554 if (value.split(':').length === 2) {
621 if (value.split(':').length === 2) {
555 value = value.split(':')[1]
622 value = value.split(':')[1]
556 }
623 }
557
624
558 var searchType = data['type'];
625 var searchType = data['type'];
559 var valueDisplay = data['value_display'];
626 var valueDisplay = data['value_display'];
560
627
561 var escapeRegExChars = function (value) {
628 var escapeRegExChars = function (value) {
562 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
629 return value.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
563 };
630 };
564 var pattern = '(' + escapeRegExChars(value) + ')';
631 var pattern = '(' + escapeRegExChars(value) + ')';
565
632
566 // highlight match
633 // highlight match
567 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
634 valueDisplay = Select2.util.escapeMarkup(valueDisplay);
568 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
635 valueDisplay = valueDisplay.replace(new RegExp(pattern, 'gi'), '<strong>$1<\/strong>');
569
636
570 var icon = '';
637 var icon = '';
571
638
572 if (searchType === 'hint') {
639 if (searchType === 'hint') {
573 icon += '<i class="icon-folder-close"></i> ';
640 icon += '<i class="icon-folder-close"></i> ';
574 }
641 }
575 else if (searchType === 'search') {
642 else if (searchType === 'search') {
576 icon += '<i class="icon-more"></i> ';
643 icon += '<i class="icon-more"></i> ';
577 }
644 }
578 else if (searchType === 'repo') {
645 else if (searchType === 'repo') {
579 if (data['repo_type'] === 'hg') {
646 if (data['repo_type'] === 'hg') {
580 icon += '<i class="icon-hg"></i> ';
647 icon += '<i class="icon-hg"></i> ';
581 }
648 }
582 else if (data['repo_type'] === 'git') {
649 else if (data['repo_type'] === 'git') {
583 icon += '<i class="icon-git"></i> ';
650 icon += '<i class="icon-git"></i> ';
584 }
651 }
585 else if (data['repo_type'] === 'svn') {
652 else if (data['repo_type'] === 'svn') {
586 icon += '<i class="icon-svn"></i> ';
653 icon += '<i class="icon-svn"></i> ';
587 }
654 }
588 if (data['private']) {
655 if (data['private']) {
589 icon += '<i class="icon-lock" ></i> ';
656 icon += '<i class="icon-lock" ></i> ';
590 }
657 }
591 else if (visualShowPublicIcon) {
658 else if (visualShowPublicIcon) {
592 icon += '<i class="icon-unlock-alt"></i> ';
659 icon += '<i class="icon-unlock-alt"></i> ';
593 }
660 }
594 }
661 }
595 else if (searchType === 'repo_group') {
662 else if (searchType === 'repo_group') {
596 icon += '<i class="icon-folder-close"></i> ';
663 icon += '<i class="icon-folder-close"></i> ';
597 }
664 }
598 else if (searchType === 'user_group') {
665 else if (searchType === 'user_group') {
599 icon += '<i class="icon-group"></i> ';
666 icon += '<i class="icon-group"></i> ';
600 }
667 }
601 else if (searchType === 'user') {
668 else if (searchType === 'user') {
602 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
669 icon += '<img class="gravatar" src="{0}"/>'.format(data['icon_link']);
603 }
670 }
604 else if (searchType === 'commit') {
671 else if (searchType === 'commit') {
605 icon += '<i class="icon-tag"></i>';
672 icon += '<i class="icon-tag"></i>';
606 }
673 }
607
674
608 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
675 var tmpl = '<div class="ac-container-wrap">{0}{1}</div>';
609 return tmpl.format(icon, valueDisplay);
676 return tmpl.format(icon, valueDisplay);
610 };
677 };
611
678
612 var handleSelect = function(element, suggestion) {
679 var handleSelect = function(element, suggestion) {
613 if (suggestion.type === "hint") {
680 if (suggestion.type === "hint") {
614 // we skip action
681 // we skip action
615 $('#main_filter').focus();
682 $('#main_filter').focus();
616 } else {
683 } else {
617 window.location = suggestion['url'];
684 window.location = suggestion['url'];
618 }
685 }
619 };
686 };
620 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
687 var autocompleteMainFilterResult = function (suggestion, originalQuery, queryLowerCase) {
621 if (queryLowerCase.split(':').length === 2) {
688 if (queryLowerCase.split(':').length === 2) {
622 queryLowerCase = queryLowerCase.split(':')[1]
689 queryLowerCase = queryLowerCase.split(':')[1]
623 }
690 }
624 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
691 return suggestion.value_display.toLowerCase().indexOf(queryLowerCase) !== -1;
625 };
692 };
626
693
627 $('#main_filter').autocomplete({
694 $('#main_filter').autocomplete({
628 serviceUrl: pyroutes.url('goto_switcher_data'),
695 serviceUrl: pyroutes.url('goto_switcher_data'),
629 params: {"search_context": templateContext.search_context},
696 params: {"search_context": templateContext.search_context},
630 minChars:2,
697 minChars:2,
631 maxHeight:400,
698 maxHeight:400,
632 deferRequestBy: 300, //miliseconds
699 deferRequestBy: 300, //miliseconds
633 tabDisabled: true,
700 tabDisabled: true,
634 autoSelectFirst: true,
701 autoSelectFirst: true,
635 formatResult: autocompleteMainFilterFormatResult,
702 formatResult: autocompleteMainFilterFormatResult,
636 lookupFilter: autocompleteMainFilterResult,
703 lookupFilter: autocompleteMainFilterResult,
637 onSelect: function (element, suggestion) {
704 onSelect: function (element, suggestion) {
638 handleSelect(element, suggestion);
705 handleSelect(element, suggestion);
639 return false;
706 return false;
640 },
707 },
641 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
708 onSearchError: function (element, query, jqXHR, textStatus, errorThrown) {
642 if (jqXHR !== 'abort') {
709 if (jqXHR !== 'abort') {
643 alert("Error during search.\nError code: {0}".format(textStatus));
710 alert("Error during search.\nError code: {0}".format(textStatus));
644 window.location = '';
711 window.location = '';
645 }
712 }
646 }
713 }
647 });
714 });
648
715
649 showMainFilterBox = function () {
716 showMainFilterBox = function () {
650 $('#main_filter_help').toggle();
717 $('#main_filter_help').toggle();
651 }
718 }
652
719
653 </script>
720 </script>
654 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
721 <script src="${h.asset('js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
655 </%def>
722 </%def>
656
723
657 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
724 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
658 <div class="modal-dialog">
725 <div class="modal-dialog">
659 <div class="modal-content">
726 <div class="modal-content">
660 <div class="modal-header">
727 <div class="modal-header">
661 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
728 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
662 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
729 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
663 </div>
730 </div>
664 <div class="modal-body">
731 <div class="modal-body">
665 <div class="block-left">
732 <div class="block-left">
666 <table class="keyboard-mappings">
733 <table class="keyboard-mappings">
667 <tbody>
734 <tbody>
668 <tr>
735 <tr>
669 <th></th>
736 <th></th>
670 <th>${_('Site-wide shortcuts')}</th>
737 <th>${_('Site-wide shortcuts')}</th>
671 </tr>
738 </tr>
672 <%
739 <%
673 elems = [
740 elems = [
674 ('/', 'Use quick search box'),
741 ('/', 'Use quick search box'),
675 ('g h', 'Goto home page'),
742 ('g h', 'Goto home page'),
676 ('g g', 'Goto my private gists page'),
743 ('g g', 'Goto my private gists page'),
677 ('g G', 'Goto my public gists page'),
744 ('g G', 'Goto my public gists page'),
678 ('g 0-9', 'Goto bookmarked items from 0-9'),
745 ('g 0-9', 'Goto bookmarked items from 0-9'),
679 ('n r', 'New repository page'),
746 ('n r', 'New repository page'),
680 ('n g', 'New gist page'),
747 ('n g', 'New gist page'),
681 ]
748 ]
682 %>
749 %>
683 %for key, desc in elems:
750 %for key, desc in elems:
684 <tr>
751 <tr>
685 <td class="keys">
752 <td class="keys">
686 <span class="key tag">${key}</span>
753 <span class="key tag">${key}</span>
687 </td>
754 </td>
688 <td>${desc}</td>
755 <td>${desc}</td>
689 </tr>
756 </tr>
690 %endfor
757 %endfor
691 </tbody>
758 </tbody>
692 </table>
759 </table>
693 </div>
760 </div>
694 <div class="block-left">
761 <div class="block-left">
695 <table class="keyboard-mappings">
762 <table class="keyboard-mappings">
696 <tbody>
763 <tbody>
697 <tr>
764 <tr>
698 <th></th>
765 <th></th>
699 <th>${_('Repositories')}</th>
766 <th>${_('Repositories')}</th>
700 </tr>
767 </tr>
701 <%
768 <%
702 elems = [
769 elems = [
703 ('g s', 'Goto summary page'),
770 ('g s', 'Goto summary page'),
704 ('g c', 'Goto changelog page'),
771 ('g c', 'Goto changelog page'),
705 ('g f', 'Goto files page'),
772 ('g f', 'Goto files page'),
706 ('g F', 'Goto files page with file search activated'),
773 ('g F', 'Goto files page with file search activated'),
707 ('g p', 'Goto pull requests page'),
774 ('g p', 'Goto pull requests page'),
708 ('g o', 'Goto repository settings'),
775 ('g o', 'Goto repository settings'),
709 ('g O', 'Goto repository permissions settings'),
776 ('g O', 'Goto repository permissions settings'),
710 ]
777 ]
711 %>
778 %>
712 %for key, desc in elems:
779 %for key, desc in elems:
713 <tr>
780 <tr>
714 <td class="keys">
781 <td class="keys">
715 <span class="key tag">${key}</span>
782 <span class="key tag">${key}</span>
716 </td>
783 </td>
717 <td>${desc}</td>
784 <td>${desc}</td>
718 </tr>
785 </tr>
719 %endfor
786 %endfor
720 </tbody>
787 </tbody>
721 </table>
788 </table>
722 </div>
789 </div>
723 </div>
790 </div>
724 <div class="modal-footer">
791 <div class="modal-footer">
725 </div>
792 </div>
726 </div><!-- /.modal-content -->
793 </div><!-- /.modal-content -->
727 </div><!-- /.modal-dialog -->
794 </div><!-- /.modal-dialog -->
728 </div><!-- /.modal -->
795 </div><!-- /.modal -->
729
796
@@ -1,141 +1,138 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2
2
3
4 <%def name="menu_bar_subnav()">
5 % if c.repo_group:
6 ${self.repo_group_menu(active='home')}
7 % endif
8 </%def>
9
10
3 <%def name="main()">
11 <%def name="main()">
4 <div class="box">
12 <div class="box">
5 <!-- box / title -->
13 <!-- box / title -->
6 <div class="title">
14 <div class="title">
7 <div class="block-left breadcrumbs">
15 % if c.repo_group:
8 ${self.breadcrumbs()}
16 ${self.repo_group_page_title(c.repo_group)}
9 <span id="match_container" style="display:none"><span id="match_count">0</span> ${_('matches')}</span>
17 ## context actions
18 <div>
19 <ul class="links icon-only-links block-right">
20 <li></li>
21 </ul>
10 </div>
22 </div>
23 % endif
24
11 %if c.rhodecode_user.username != h.DEFAULT_USER:
25 %if c.rhodecode_user.username != h.DEFAULT_USER:
12 <div class="block-right">
26 <div class="block-right">
13 <%
27 <%
14 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
28 is_admin = h.HasPermissionAny('hg.admin')('can create repos index page')
15 create_repo = h.HasPermissionAny('hg.create.repository')('can create repository index page')
29 create_repo = h.HasPermissionAny('hg.create.repository')('can create repository index page')
16 create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')('can create repository groups index page')
30 create_repo_group = h.HasPermissionAny('hg.repogroup.create.true')('can create repository groups index page')
17 create_user_group = h.HasPermissionAny('hg.usergroup.create.true')('can create user groups index page')
31 create_user_group = h.HasPermissionAny('hg.usergroup.create.true')('can create user groups index page')
18
19 gr_name = c.repo_group.group_name if c.repo_group else None
20 # create repositories with write permission on group is set to true
21 create_on_write = h.HasPermissionAny('hg.create.write_on_repogroup.true')()
22 group_admin = h.HasRepoGroupPermissionAny('group.admin')(gr_name, 'group admin index page')
23 group_write = h.HasRepoGroupPermissionAny('group.write')(gr_name, 'can write into group index page')
24 %>
32 %>
25
33
26 %if not c.repo_group:
34 %if not c.repo_group:
27 ## no repository group context here
35 ## no repository group context here
28 %if is_admin or create_repo:
36 %if is_admin or create_repo:
29 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
37 <a href="${h.route_path('repo_new')}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
30 %endif
38 %endif
31
39
32 %if is_admin or create_repo_group:
40 %if is_admin or create_repo_group:
33 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
41 <a href="${h.route_path('repo_group_new')}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
34 %endif
42 %endif
35 %else:
36 ##we're inside other repository group other terms apply
37 %if is_admin or group_admin or (group_write and create_on_write):
38 <a href="${h.route_path('repo_new',_query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-success btn-primary">${_('Add Repository')}</a>
39 %endif
40 %if is_admin or group_admin:
41 <a href="${h.route_path('repo_group_new',_query=dict(parent_group=c.repo_group.group_id))}" class="btn btn-small btn-default">${_(u'Add Repository Group')}</a>
42 %endif
43 %if is_admin or group_admin:
44 <a href="${h.route_path('edit_repo_group',repo_group_name=c.repo_group.group_name)}" title="${_('You have admin right to this group, and can edit it')}" class="btn btn-small btn-primary">${_('Edit Repository Group')}</a>
45 %endif
46 %endif
43 %endif
47 </div>
44 </div>
48 %endif
45 %endif
49 </div>
46 </div>
50 <!-- end box / title -->
47 <!-- end box / title -->
51 <div class="table">
48 <div class="table">
52 <div id="groups_list_wrap">
49 <div id="groups_list_wrap">
53 <table id="group_list_table" class="display" style="width: 100%"></table>
50 <table id="group_list_table" class="display" style="width: 100%"></table>
54 </div>
51 </div>
55 </div>
52 </div>
56
53
57 <div class="table">
54 <div class="table">
58 <div id="repos_list_wrap">
55 <div id="repos_list_wrap">
59 <table id="repo_list_table" class="display" style="width: 100%"></table>
56 <table id="repo_list_table" class="display" style="width: 100%"></table>
60 </div>
57 </div>
61 </div>
58 </div>
62
59
63 ## no repository groups and repos present, show something to the users
60 ## no repository groups and repos present, show something to the users
64 % if c.repo_groups_data == '[]' and c.repos_data == '[]':
61 % if c.repo_groups_data == '[]' and c.repos_data == '[]':
65 <div class="table">
62 <div class="table">
66 <h2 class="no-object-border">
63 <h2 class="no-object-border">
67 ${_('No repositories or repositories groups exists here.')}
64 ${_('No repositories or repositories groups exists here.')}
68 </h2>
65 </h2>
69 </div>
66 </div>
70 % endif
67 % endif
71
68
72 </div>
69 </div>
73 <script>
70 <script>
74 $(document).ready(function() {
71 $(document).ready(function() {
75
72
76 // repo group list
73 // repo group list
77 % if c.repo_groups_data != '[]':
74 % if c.repo_groups_data != '[]':
78 $('#group_list_table').DataTable({
75 $('#group_list_table').DataTable({
79 data: ${c.repo_groups_data|n},
76 data: ${c.repo_groups_data|n},
80 dom: 'rtp',
77 dom: 'rtp',
81 pageLength: ${c.visual.dashboard_items},
78 pageLength: ${c.visual.dashboard_items},
82 order: [[ 0, "asc" ]],
79 order: [[ 0, "asc" ]],
83 columns: [
80 columns: [
84 { data: {"_": "name",
81 { data: {"_": "name",
85 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
82 "sort": "name_raw"}, title: "${_('Name')}", className: "td-componentname" },
86 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
83 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
87 { data: {"_": "desc",
84 { data: {"_": "desc",
88 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
85 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
89 { data: {"_": "last_change",
86 { data: {"_": "last_change",
90 "sort": "last_change_raw",
87 "sort": "last_change_raw",
91 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
88 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
92 { data: {"_": "owner",
89 { data: {"_": "owner",
93 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
90 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
94 ],
91 ],
95 language: {
92 language: {
96 paginate: DEFAULT_GRID_PAGINATION,
93 paginate: DEFAULT_GRID_PAGINATION,
97 emptyTable: _gettext("No repository groups available yet.")
94 emptyTable: _gettext("No repository groups available yet.")
98 },
95 },
99 "drawCallback": function( settings, json ) {
96 "drawCallback": function( settings, json ) {
100 timeagoActivate();
97 timeagoActivate();
101 quick_repo_menu();
98 quick_repo_menu();
102 }
99 }
103 });
100 });
104 % endif
101 % endif
105
102
106 // repo list
103 // repo list
107 % if c.repos_data != '[]':
104 % if c.repos_data != '[]':
108 $('#repo_list_table').DataTable({
105 $('#repo_list_table').DataTable({
109 data: ${c.repos_data|n},
106 data: ${c.repos_data|n},
110 dom: 'rtp',
107 dom: 'rtp',
111 order: [[ 0, "asc" ]],
108 order: [[ 0, "asc" ]],
112 pageLength: ${c.visual.dashboard_items},
109 pageLength: ${c.visual.dashboard_items},
113 columns: [
110 columns: [
114 { data: {"_": "name",
111 { data: {"_": "name",
115 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-componentname" },
112 "sort": "name_raw"}, title: "${_('Name')}", className: "truncate-wrap td-componentname" },
116 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
113 { data: 'menu', "bSortable": false, className: "quick_repo_menu" },
117 { data: {"_": "desc",
114 { data: {"_": "desc",
118 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
115 "sort": "desc"}, title: "${_('Description')}", className: "td-description" },
119 { data: {"_": "last_change",
116 { data: {"_": "last_change",
120 "sort": "last_change_raw",
117 "sort": "last_change_raw",
121 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
118 "type": Number}, title: "${_('Last Change')}", className: "td-time" },
122 { data: {"_": "last_changeset",
119 { data: {"_": "last_changeset",
123 "sort": "last_changeset_raw",
120 "sort": "last_changeset_raw",
124 "type": Number}, title: "${_('Commit')}", className: "td-hash" },
121 "type": Number}, title: "${_('Commit')}", className: "td-hash" },
125 { data: {"_": "owner",
122 { data: {"_": "owner",
126 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
123 "sort": "owner"}, title: "${_('Owner')}", className: "td-user" }
127 ],
124 ],
128 language: {
125 language: {
129 paginate: DEFAULT_GRID_PAGINATION,
126 paginate: DEFAULT_GRID_PAGINATION,
130 emptyTable: _gettext("No repositories available yet.")
127 emptyTable: _gettext("No repositories available yet.")
131 },
128 },
132 "drawCallback": function( settings, json ) {
129 "drawCallback": function( settings, json ) {
133 timeagoActivate();
130 timeagoActivate();
134 quick_repo_menu();
131 quick_repo_menu();
135 }
132 }
136 });
133 });
137 % endif
134 % endif
138
135
139 });
136 });
140 </script>
137 </script>
141 </%def>
138 </%def>
@@ -1,548 +1,548 b''
1 <%inherit file="/base/base.mako"/>
1 <%inherit file="/base/base.mako"/>
2 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
2 <%namespace name="dt" file="/data_table/_dt_elements.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${c.repo_name} ${_('New pull request')}
5 ${c.repo_name} ${_('New pull request')}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${_('New pull request')}
9 ${_('New pull request')}
10 </%def>
10 </%def>
11
11
12 <%def name="menu_bar_nav()">
12 <%def name="menu_bar_nav()">
13 ${self.menu_items(active='repositories')}
13 ${self.menu_items(active='repositories')}
14 </%def>
14 </%def>
15
15
16 <%def name="menu_bar_subnav()">
16 <%def name="menu_bar_subnav()">
17 ${self.repo_menu(active='showpullrequest')}
17 ${self.repo_menu(active='showpullrequest')}
18 </%def>
18 </%def>
19
19
20 <%def name="main()">
20 <%def name="main()">
21 <div class="box">
21 <div class="box">
22 <div class="title">
22 <div class="title">
23 ${self.repo_page_title(c.rhodecode_db_repo)}
23 ${self.repo_page_title(c.rhodecode_db_repo)}
24 </div>
24 </div>
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 ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)}
27
27
28 ${self.breadcrumbs()}
28 ${self.breadcrumbs()}
29
29
30 <div class="box pr-summary">
30 <div class="box pr-summary">
31
31
32 <div class="summary-details block-left">
32 <div class="summary-details block-left">
33
33
34
34
35 <div class="pr-details-title">
35 <div class="pr-details-title">
36 ${_('Pull request summary')}
36 ${_('Summary')}
37 </div>
37 </div>
38
38
39 <div class="form" style="padding-top: 10px">
39 <div class="form" style="padding-top: 10px">
40 <!-- fields -->
40 <!-- fields -->
41
41
42 <div class="fields" >
42 <div class="fields" >
43
43
44 <div class="field">
44 <div class="field">
45 <div class="label">
45 <div class="label">
46 <label for="pullrequest_title">${_('Title')}:</label>
46 <label for="pullrequest_title">${_('Title')}:</label>
47 </div>
47 </div>
48 <div class="input">
48 <div class="input">
49 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
49 ${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")}
50 </div>
50 </div>
51 </div>
51 </div>
52
52
53 <div class="field">
53 <div class="field">
54 <div class="label label-textarea">
54 <div class="label label-textarea">
55 <label for="pullrequest_desc">${_('Description')}:</label>
55 <label for="pullrequest_desc">${_('Description')}:</label>
56 </div>
56 </div>
57 <div class="textarea text-area editor">
57 <div class="textarea text-area editor">
58 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
58 <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}">
59 ${dt.markup_form('pullrequest_desc')}
59 ${dt.markup_form('pullrequest_desc')}
60 </div>
60 </div>
61 </div>
61 </div>
62
62
63 <div class="field">
63 <div class="field">
64 <div class="label label-textarea">
64 <div class="label label-textarea">
65 <label for="commit_flow">${_('Commit flow')}:</label>
65 <label for="commit_flow">${_('Commit flow')}:</label>
66 </div>
66 </div>
67
67
68 ## TODO: johbo: Abusing the "content" class here to get the
68 ## TODO: johbo: Abusing the "content" class here to get the
69 ## desired effect. Should be replaced by a proper solution.
69 ## desired effect. Should be replaced by a proper solution.
70
70
71 ##ORG
71 ##ORG
72 <div class="content">
72 <div class="content">
73 <strong>${_('Source repository')}:</strong>
73 <strong>${_('Source repository')}:</strong>
74 ${c.rhodecode_db_repo.description}
74 ${c.rhodecode_db_repo.description}
75 </div>
75 </div>
76 <div class="content">
76 <div class="content">
77 ${h.hidden('source_repo')}
77 ${h.hidden('source_repo')}
78 ${h.hidden('source_ref')}
78 ${h.hidden('source_ref')}
79 </div>
79 </div>
80
80
81 ##OTHER, most Probably the PARENT OF THIS FORK
81 ##OTHER, most Probably the PARENT OF THIS FORK
82 <div class="content">
82 <div class="content">
83 ## filled with JS
83 ## filled with JS
84 <div id="target_repo_desc"></div>
84 <div id="target_repo_desc"></div>
85 </div>
85 </div>
86
86
87 <div class="content">
87 <div class="content">
88 ${h.hidden('target_repo')}
88 ${h.hidden('target_repo')}
89 ${h.hidden('target_ref')}
89 ${h.hidden('target_ref')}
90 <span id="target_ref_loading" style="display: none">
90 <span id="target_ref_loading" style="display: none">
91 ${_('Loading refs...')}
91 ${_('Loading refs...')}
92 </span>
92 </span>
93 </div>
93 </div>
94 </div>
94 </div>
95
95
96 <div class="field">
96 <div class="field">
97 <div class="label label-textarea">
97 <div class="label label-textarea">
98 <label for="pullrequest_submit"></label>
98 <label for="pullrequest_submit"></label>
99 </div>
99 </div>
100 <div class="input">
100 <div class="input">
101 <div class="pr-submit-button">
101 <div class="pr-submit-button">
102 <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
102 <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}">
103 </div>
103 </div>
104 <div id="pr_open_message"></div>
104 <div id="pr_open_message"></div>
105 </div>
105 </div>
106 </div>
106 </div>
107
107
108 <div class="pr-spacing-container"></div>
108 <div class="pr-spacing-container"></div>
109 </div>
109 </div>
110 </div>
110 </div>
111 </div>
111 </div>
112 <div>
112 <div>
113 ## AUTHOR
113 ## AUTHOR
114 <div class="reviewers-title block-right">
114 <div class="reviewers-title block-right">
115 <div class="pr-details-title">
115 <div class="pr-details-title">
116 ${_('Author of this pull request')}
116 ${_('Author of this pull request')}
117 </div>
117 </div>
118 </div>
118 </div>
119 <div class="block-right pr-details-content reviewers">
119 <div class="block-right pr-details-content reviewers">
120 <ul class="group_members">
120 <ul class="group_members">
121 <li>
121 <li>
122 ${self.gravatar_with_user(c.rhodecode_user.email, 16)}
122 ${self.gravatar_with_user(c.rhodecode_user.email, 16)}
123 </li>
123 </li>
124 </ul>
124 </ul>
125 </div>
125 </div>
126
126
127 ## REVIEW RULES
127 ## REVIEW RULES
128 <div id="review_rules" style="display: none" class="reviewers-title block-right">
128 <div id="review_rules" style="display: none" class="reviewers-title block-right">
129 <div class="pr-details-title">
129 <div class="pr-details-title">
130 ${_('Reviewer rules')}
130 ${_('Reviewer rules')}
131 </div>
131 </div>
132 <div class="pr-reviewer-rules">
132 <div class="pr-reviewer-rules">
133 ## review rules will be appended here, by default reviewers logic
133 ## review rules will be appended here, by default reviewers logic
134 </div>
134 </div>
135 </div>
135 </div>
136
136
137 ## REVIEWERS
137 ## REVIEWERS
138 <div class="reviewers-title block-right">
138 <div class="reviewers-title block-right">
139 <div class="pr-details-title">
139 <div class="pr-details-title">
140 ${_('Pull request reviewers')}
140 ${_('Pull request reviewers')}
141 <span class="calculate-reviewers"> - ${_('loading...')}</span>
141 <span class="calculate-reviewers"> - ${_('loading...')}</span>
142 </div>
142 </div>
143 </div>
143 </div>
144 <div id="reviewers" class="block-right pr-details-content reviewers">
144 <div id="reviewers" class="block-right pr-details-content reviewers">
145 ## members goes here, filled via JS based on initial selection !
145 ## members goes here, filled via JS based on initial selection !
146 <input type="hidden" name="__start__" value="review_members:sequence">
146 <input type="hidden" name="__start__" value="review_members:sequence">
147 <ul id="review_members" class="group_members"></ul>
147 <ul id="review_members" class="group_members"></ul>
148 <input type="hidden" name="__end__" value="review_members:sequence">
148 <input type="hidden" name="__end__" value="review_members:sequence">
149 <div id="add_reviewer_input" class='ac'>
149 <div id="add_reviewer_input" class='ac'>
150 <div class="reviewer_ac">
150 <div class="reviewer_ac">
151 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
151 ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))}
152 <div id="reviewers_container"></div>
152 <div id="reviewers_container"></div>
153 </div>
153 </div>
154 </div>
154 </div>
155 </div>
155 </div>
156 </div>
156 </div>
157 </div>
157 </div>
158 <div class="box">
158 <div class="box">
159 <div>
159 <div>
160 ## overview pulled by ajax
160 ## overview pulled by ajax
161 <div id="pull_request_overview"></div>
161 <div id="pull_request_overview"></div>
162 </div>
162 </div>
163 </div>
163 </div>
164 ${h.end_form()}
164 ${h.end_form()}
165 </div>
165 </div>
166
166
167 <script type="text/javascript">
167 <script type="text/javascript">
168 $(function(){
168 $(function(){
169 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
169 var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}';
170 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
170 var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n};
171 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
171 var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}';
172 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
172 var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n};
173
173
174 var $pullRequestForm = $('#pull_request_form');
174 var $pullRequestForm = $('#pull_request_form');
175 var $pullRequestSubmit = $('#pr_submit', $pullRequestForm);
175 var $pullRequestSubmit = $('#pr_submit', $pullRequestForm);
176 var $sourceRepo = $('#source_repo', $pullRequestForm);
176 var $sourceRepo = $('#source_repo', $pullRequestForm);
177 var $targetRepo = $('#target_repo', $pullRequestForm);
177 var $targetRepo = $('#target_repo', $pullRequestForm);
178 var $sourceRef = $('#source_ref', $pullRequestForm);
178 var $sourceRef = $('#source_ref', $pullRequestForm);
179 var $targetRef = $('#target_ref', $pullRequestForm);
179 var $targetRef = $('#target_ref', $pullRequestForm);
180
180
181 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
181 var sourceRepo = function() { return $sourceRepo.eq(0).val() };
182 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
182 var sourceRef = function() { return $sourceRef.eq(0).val().split(':') };
183
183
184 var targetRepo = function() { return $targetRepo.eq(0).val() };
184 var targetRepo = function() { return $targetRepo.eq(0).val() };
185 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
185 var targetRef = function() { return $targetRef.eq(0).val().split(':') };
186
186
187 var calculateContainerWidth = function() {
187 var calculateContainerWidth = function() {
188 var maxWidth = 0;
188 var maxWidth = 0;
189 var repoSelect2Containers = ['#source_repo', '#target_repo'];
189 var repoSelect2Containers = ['#source_repo', '#target_repo'];
190 $.each(repoSelect2Containers, function(idx, value) {
190 $.each(repoSelect2Containers, function(idx, value) {
191 $(value).select2('container').width('auto');
191 $(value).select2('container').width('auto');
192 var curWidth = $(value).select2('container').width();
192 var curWidth = $(value).select2('container').width();
193 if (maxWidth <= curWidth) {
193 if (maxWidth <= curWidth) {
194 maxWidth = curWidth;
194 maxWidth = curWidth;
195 }
195 }
196 $.each(repoSelect2Containers, function(idx, value) {
196 $.each(repoSelect2Containers, function(idx, value) {
197 $(value).select2('container').width(maxWidth + 10);
197 $(value).select2('container').width(maxWidth + 10);
198 });
198 });
199 });
199 });
200 };
200 };
201
201
202 var initRefSelection = function(selectedRef) {
202 var initRefSelection = function(selectedRef) {
203 return function(element, callback) {
203 return function(element, callback) {
204 // translate our select2 id into a text, it's a mapping to show
204 // translate our select2 id into a text, it's a mapping to show
205 // simple label when selecting by internal ID.
205 // simple label when selecting by internal ID.
206 var id, refData;
206 var id, refData;
207 if (selectedRef === undefined || selectedRef === null) {
207 if (selectedRef === undefined || selectedRef === null) {
208 id = element.val();
208 id = element.val();
209 refData = element.val().split(':');
209 refData = element.val().split(':');
210
210
211 if (refData.length !== 3){
211 if (refData.length !== 3){
212 refData = ["", "", ""]
212 refData = ["", "", ""]
213 }
213 }
214 } else {
214 } else {
215 id = selectedRef;
215 id = selectedRef;
216 refData = selectedRef.split(':');
216 refData = selectedRef.split(':');
217 }
217 }
218
218
219 var text = refData[1];
219 var text = refData[1];
220 if (refData[0] === 'rev') {
220 if (refData[0] === 'rev') {
221 text = text.substring(0, 12);
221 text = text.substring(0, 12);
222 }
222 }
223
223
224 var data = {id: id, text: text};
224 var data = {id: id, text: text};
225 callback(data);
225 callback(data);
226 };
226 };
227 };
227 };
228
228
229 var formatRefSelection = function(data, container, escapeMarkup) {
229 var formatRefSelection = function(data, container, escapeMarkup) {
230 var prefix = '';
230 var prefix = '';
231 var refData = data.id.split(':');
231 var refData = data.id.split(':');
232 if (refData[0] === 'branch') {
232 if (refData[0] === 'branch') {
233 prefix = '<i class="icon-branch"></i>';
233 prefix = '<i class="icon-branch"></i>';
234 }
234 }
235 else if (refData[0] === 'book') {
235 else if (refData[0] === 'book') {
236 prefix = '<i class="icon-bookmark"></i>';
236 prefix = '<i class="icon-bookmark"></i>';
237 }
237 }
238 else if (refData[0] === 'tag') {
238 else if (refData[0] === 'tag') {
239 prefix = '<i class="icon-tag"></i>';
239 prefix = '<i class="icon-tag"></i>';
240 }
240 }
241
241
242 var originalOption = data.element;
242 var originalOption = data.element;
243 return prefix + escapeMarkup(data.text);
243 return prefix + escapeMarkup(data.text);
244 };formatSelection:
244 };formatSelection:
245
245
246 // custom code mirror
246 // custom code mirror
247 var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm;
247 var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm;
248
248
249 reviewersController = new ReviewersController();
249 reviewersController = new ReviewersController();
250
250
251 var queryTargetRepo = function(self, query) {
251 var queryTargetRepo = function(self, query) {
252 // cache ALL results if query is empty
252 // cache ALL results if query is empty
253 var cacheKey = query.term || '__';
253 var cacheKey = query.term || '__';
254 var cachedData = self.cachedDataSource[cacheKey];
254 var cachedData = self.cachedDataSource[cacheKey];
255
255
256 if (cachedData) {
256 if (cachedData) {
257 query.callback({results: cachedData.results});
257 query.callback({results: cachedData.results});
258 } else {
258 } else {
259 $.ajax({
259 $.ajax({
260 url: pyroutes.url('pullrequest_repo_targets', {'repo_name': templateContext.repo_name}),
260 url: pyroutes.url('pullrequest_repo_targets', {'repo_name': templateContext.repo_name}),
261 data: {query: query.term},
261 data: {query: query.term},
262 dataType: 'json',
262 dataType: 'json',
263 type: 'GET',
263 type: 'GET',
264 success: function(data) {
264 success: function(data) {
265 self.cachedDataSource[cacheKey] = data;
265 self.cachedDataSource[cacheKey] = data;
266 query.callback({results: data.results});
266 query.callback({results: data.results});
267 },
267 },
268 error: function(data, textStatus, errorThrown) {
268 error: function(data, textStatus, errorThrown) {
269 alert(
269 alert(
270 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
270 "Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
271 }
271 }
272 });
272 });
273 }
273 }
274 };
274 };
275
275
276 var queryTargetRefs = function(initialData, query) {
276 var queryTargetRefs = function(initialData, query) {
277 var data = {results: []};
277 var data = {results: []};
278 // filter initialData
278 // filter initialData
279 $.each(initialData, function() {
279 $.each(initialData, function() {
280 var section = this.text;
280 var section = this.text;
281 var children = [];
281 var children = [];
282 $.each(this.children, function() {
282 $.each(this.children, function() {
283 if (query.term.length === 0 ||
283 if (query.term.length === 0 ||
284 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
284 this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) {
285 children.push({'id': this.id, 'text': this.text})
285 children.push({'id': this.id, 'text': this.text})
286 }
286 }
287 });
287 });
288 data.results.push({'text': section, 'children': children})
288 data.results.push({'text': section, 'children': children})
289 });
289 });
290 query.callback({results: data.results});
290 query.callback({results: data.results});
291 };
291 };
292
292
293 var loadRepoRefDiffPreview = function() {
293 var loadRepoRefDiffPreview = function() {
294
294
295 var url_data = {
295 var url_data = {
296 'repo_name': targetRepo(),
296 'repo_name': targetRepo(),
297 'target_repo': sourceRepo(),
297 'target_repo': sourceRepo(),
298 'source_ref': targetRef()[2],
298 'source_ref': targetRef()[2],
299 'source_ref_type': 'rev',
299 'source_ref_type': 'rev',
300 'target_ref': sourceRef()[2],
300 'target_ref': sourceRef()[2],
301 'target_ref_type': 'rev',
301 'target_ref_type': 'rev',
302 'merge': true,
302 'merge': true,
303 '_': Date.now() // bypass browser caching
303 '_': Date.now() // bypass browser caching
304 }; // gather the source/target ref and repo here
304 }; // gather the source/target ref and repo here
305
305
306 if (sourceRef().length !== 3 || targetRef().length !== 3) {
306 if (sourceRef().length !== 3 || targetRef().length !== 3) {
307 prButtonLock(true, "${_('Please select source and target')}");
307 prButtonLock(true, "${_('Please select source and target')}");
308 return;
308 return;
309 }
309 }
310 var url = pyroutes.url('repo_compare', url_data);
310 var url = pyroutes.url('repo_compare', url_data);
311
311
312 // lock PR button, so we cannot send PR before it's calculated
312 // lock PR button, so we cannot send PR before it's calculated
313 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
313 prButtonLock(true, "${_('Loading compare ...')}", 'compare');
314
314
315 if (loadRepoRefDiffPreview._currentRequest) {
315 if (loadRepoRefDiffPreview._currentRequest) {
316 loadRepoRefDiffPreview._currentRequest.abort();
316 loadRepoRefDiffPreview._currentRequest.abort();
317 }
317 }
318
318
319 loadRepoRefDiffPreview._currentRequest = $.get(url)
319 loadRepoRefDiffPreview._currentRequest = $.get(url)
320 .error(function(data, textStatus, errorThrown) {
320 .error(function(data, textStatus, errorThrown) {
321 if (textStatus !== 'abort') {
321 if (textStatus !== 'abort') {
322 alert(
322 alert(
323 "Error while processing request.\nError code {0} ({1}).".format(
323 "Error while processing request.\nError code {0} ({1}).".format(
324 data.status, data.statusText));
324 data.status, data.statusText));
325 }
325 }
326
326
327 })
327 })
328 .done(function(data) {
328 .done(function(data) {
329 loadRepoRefDiffPreview._currentRequest = null;
329 loadRepoRefDiffPreview._currentRequest = null;
330 $('#pull_request_overview').html(data);
330 $('#pull_request_overview').html(data);
331
331
332 var commitElements = $(data).find('tr[commit_id]');
332 var commitElements = $(data).find('tr[commit_id]');
333
333
334 var prTitleAndDesc = getTitleAndDescription(
334 var prTitleAndDesc = getTitleAndDescription(
335 sourceRef()[1], commitElements, 5);
335 sourceRef()[1], commitElements, 5);
336
336
337 var title = prTitleAndDesc[0];
337 var title = prTitleAndDesc[0];
338 var proposedDescription = prTitleAndDesc[1];
338 var proposedDescription = prTitleAndDesc[1];
339
339
340 var useGeneratedTitle = (
340 var useGeneratedTitle = (
341 $('#pullrequest_title').hasClass('autogenerated-title') ||
341 $('#pullrequest_title').hasClass('autogenerated-title') ||
342 $('#pullrequest_title').val() === "");
342 $('#pullrequest_title').val() === "");
343
343
344 if (title && useGeneratedTitle) {
344 if (title && useGeneratedTitle) {
345 // use generated title if we haven't specified our own
345 // use generated title if we haven't specified our own
346 $('#pullrequest_title').val(title);
346 $('#pullrequest_title').val(title);
347 $('#pullrequest_title').addClass('autogenerated-title');
347 $('#pullrequest_title').addClass('autogenerated-title');
348
348
349 }
349 }
350
350
351 var useGeneratedDescription = (
351 var useGeneratedDescription = (
352 !codeMirrorInstance._userDefinedValue ||
352 !codeMirrorInstance._userDefinedValue ||
353 codeMirrorInstance.getValue() === "");
353 codeMirrorInstance.getValue() === "");
354
354
355 if (proposedDescription && useGeneratedDescription) {
355 if (proposedDescription && useGeneratedDescription) {
356 // set proposed content, if we haven't defined our own,
356 // set proposed content, if we haven't defined our own,
357 // or we don't have description written
357 // or we don't have description written
358 codeMirrorInstance._userDefinedValue = false; // reset state
358 codeMirrorInstance._userDefinedValue = false; // reset state
359 codeMirrorInstance.setValue(proposedDescription);
359 codeMirrorInstance.setValue(proposedDescription);
360 }
360 }
361
361
362 // refresh our codeMirror so events kicks in and it's change aware
362 // refresh our codeMirror so events kicks in and it's change aware
363 codeMirrorInstance.refresh();
363 codeMirrorInstance.refresh();
364
364
365 var msg = '';
365 var msg = '';
366 if (commitElements.length === 1) {
366 if (commitElements.length === 1) {
367 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
367 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}";
368 } else {
368 } else {
369 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
369 msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}";
370 }
370 }
371
371
372 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
372 msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url);
373
373
374 if (commitElements.length) {
374 if (commitElements.length) {
375 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
375 var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length);
376 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
376 prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare');
377 }
377 }
378 else {
378 else {
379 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
379 prButtonLock(true, "${_('There are no commits to merge.')}", 'compare');
380 }
380 }
381
381
382
382
383 });
383 });
384 };
384 };
385
385
386 var Select2Box = function(element, overrides) {
386 var Select2Box = function(element, overrides) {
387 var globalDefaults = {
387 var globalDefaults = {
388 dropdownAutoWidth: true,
388 dropdownAutoWidth: true,
389 containerCssClass: "drop-menu",
389 containerCssClass: "drop-menu",
390 dropdownCssClass: "drop-menu-dropdown"
390 dropdownCssClass: "drop-menu-dropdown"
391 };
391 };
392
392
393 var initSelect2 = function(defaultOptions) {
393 var initSelect2 = function(defaultOptions) {
394 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
394 var options = jQuery.extend(globalDefaults, defaultOptions, overrides);
395 element.select2(options);
395 element.select2(options);
396 };
396 };
397
397
398 return {
398 return {
399 initRef: function() {
399 initRef: function() {
400 var defaultOptions = {
400 var defaultOptions = {
401 minimumResultsForSearch: 5,
401 minimumResultsForSearch: 5,
402 formatSelection: formatRefSelection
402 formatSelection: formatRefSelection
403 };
403 };
404
404
405 initSelect2(defaultOptions);
405 initSelect2(defaultOptions);
406 },
406 },
407
407
408 initRepo: function(defaultValue, readOnly) {
408 initRepo: function(defaultValue, readOnly) {
409 var defaultOptions = {
409 var defaultOptions = {
410 initSelection : function (element, callback) {
410 initSelection : function (element, callback) {
411 var data = {id: defaultValue, text: defaultValue};
411 var data = {id: defaultValue, text: defaultValue};
412 callback(data);
412 callback(data);
413 }
413 }
414 };
414 };
415
415
416 initSelect2(defaultOptions);
416 initSelect2(defaultOptions);
417
417
418 element.select2('val', defaultSourceRepo);
418 element.select2('val', defaultSourceRepo);
419 if (readOnly === true) {
419 if (readOnly === true) {
420 element.select2('readonly', true);
420 element.select2('readonly', true);
421 }
421 }
422 }
422 }
423 };
423 };
424 };
424 };
425
425
426 var initTargetRefs = function(refsData, selectedRef) {
426 var initTargetRefs = function(refsData, selectedRef) {
427
427
428 Select2Box($targetRef, {
428 Select2Box($targetRef, {
429 placeholder: "${_('Select commit reference')}",
429 placeholder: "${_('Select commit reference')}",
430 query: function(query) {
430 query: function(query) {
431 queryTargetRefs(refsData, query);
431 queryTargetRefs(refsData, query);
432 },
432 },
433 initSelection : initRefSelection(selectedRef)
433 initSelection : initRefSelection(selectedRef)
434 }).initRef();
434 }).initRef();
435
435
436 if (!(selectedRef === undefined)) {
436 if (!(selectedRef === undefined)) {
437 $targetRef.select2('val', selectedRef);
437 $targetRef.select2('val', selectedRef);
438 }
438 }
439 };
439 };
440
440
441 var targetRepoChanged = function(repoData) {
441 var targetRepoChanged = function(repoData) {
442 // generate new DESC of target repo displayed next to select
442 // generate new DESC of target repo displayed next to select
443 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
443 var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']});
444 $('#target_repo_desc').html(
444 $('#target_repo_desc').html(
445 "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
445 "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink)
446 );
446 );
447
447
448 // generate dynamic select2 for refs.
448 // generate dynamic select2 for refs.
449 initTargetRefs(repoData['refs']['select2_refs'],
449 initTargetRefs(repoData['refs']['select2_refs'],
450 repoData['refs']['selected_ref']);
450 repoData['refs']['selected_ref']);
451
451
452 };
452 };
453
453
454 var sourceRefSelect2 = Select2Box($sourceRef, {
454 var sourceRefSelect2 = Select2Box($sourceRef, {
455 placeholder: "${_('Select commit reference')}",
455 placeholder: "${_('Select commit reference')}",
456 query: function(query) {
456 query: function(query) {
457 var initialData = defaultSourceRepoData['refs']['select2_refs'];
457 var initialData = defaultSourceRepoData['refs']['select2_refs'];
458 queryTargetRefs(initialData, query)
458 queryTargetRefs(initialData, query)
459 },
459 },
460 initSelection: initRefSelection()
460 initSelection: initRefSelection()
461 }
461 }
462 );
462 );
463
463
464 var sourceRepoSelect2 = Select2Box($sourceRepo, {
464 var sourceRepoSelect2 = Select2Box($sourceRepo, {
465 query: function(query) {}
465 query: function(query) {}
466 });
466 });
467
467
468 var targetRepoSelect2 = Select2Box($targetRepo, {
468 var targetRepoSelect2 = Select2Box($targetRepo, {
469 cachedDataSource: {},
469 cachedDataSource: {},
470 query: $.debounce(250, function(query) {
470 query: $.debounce(250, function(query) {
471 queryTargetRepo(this, query);
471 queryTargetRepo(this, query);
472 }),
472 }),
473 formatResult: formatRepoResult
473 formatResult: formatRepoResult
474 });
474 });
475
475
476 sourceRefSelect2.initRef();
476 sourceRefSelect2.initRef();
477
477
478 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
478 sourceRepoSelect2.initRepo(defaultSourceRepo, true);
479
479
480 targetRepoSelect2.initRepo(defaultTargetRepo, false);
480 targetRepoSelect2.initRepo(defaultTargetRepo, false);
481
481
482 $sourceRef.on('change', function(e){
482 $sourceRef.on('change', function(e){
483 loadRepoRefDiffPreview();
483 loadRepoRefDiffPreview();
484 reviewersController.loadDefaultReviewers(
484 reviewersController.loadDefaultReviewers(
485 sourceRepo(), sourceRef(), targetRepo(), targetRef());
485 sourceRepo(), sourceRef(), targetRepo(), targetRef());
486 });
486 });
487
487
488 $targetRef.on('change', function(e){
488 $targetRef.on('change', function(e){
489 loadRepoRefDiffPreview();
489 loadRepoRefDiffPreview();
490 reviewersController.loadDefaultReviewers(
490 reviewersController.loadDefaultReviewers(
491 sourceRepo(), sourceRef(), targetRepo(), targetRef());
491 sourceRepo(), sourceRef(), targetRepo(), targetRef());
492 });
492 });
493
493
494 $targetRepo.on('change', function(e){
494 $targetRepo.on('change', function(e){
495 var repoName = $(this).val();
495 var repoName = $(this).val();
496 calculateContainerWidth();
496 calculateContainerWidth();
497 $targetRef.select2('destroy');
497 $targetRef.select2('destroy');
498 $('#target_ref_loading').show();
498 $('#target_ref_loading').show();
499
499
500 $.ajax({
500 $.ajax({
501 url: pyroutes.url('pullrequest_repo_refs',
501 url: pyroutes.url('pullrequest_repo_refs',
502 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
502 {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}),
503 data: {},
503 data: {},
504 dataType: 'json',
504 dataType: 'json',
505 type: 'GET',
505 type: 'GET',
506 success: function(data) {
506 success: function(data) {
507 $('#target_ref_loading').hide();
507 $('#target_ref_loading').hide();
508 targetRepoChanged(data);
508 targetRepoChanged(data);
509 loadRepoRefDiffPreview();
509 loadRepoRefDiffPreview();
510 },
510 },
511 error: function(data, textStatus, errorThrown) {
511 error: function(data, textStatus, errorThrown) {
512 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
512 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
513 }
513 }
514 })
514 })
515
515
516 });
516 });
517
517
518 $pullRequestForm.on('submit', function(e){
518 $pullRequestForm.on('submit', function(e){
519 // Flush changes into textarea
519 // Flush changes into textarea
520 codeMirrorInstance.save();
520 codeMirrorInstance.save();
521 prButtonLock(true, null, 'all');
521 prButtonLock(true, null, 'all');
522 });
522 });
523
523
524 prButtonLock(true, "${_('Please select source and target')}", 'all');
524 prButtonLock(true, "${_('Please select source and target')}", 'all');
525
525
526 // auto-load on init, the target refs select2
526 // auto-load on init, the target refs select2
527 calculateContainerWidth();
527 calculateContainerWidth();
528 targetRepoChanged(defaultTargetRepoData);
528 targetRepoChanged(defaultTargetRepoData);
529
529
530 $('#pullrequest_title').on('keyup', function(e){
530 $('#pullrequest_title').on('keyup', function(e){
531 $(this).removeClass('autogenerated-title');
531 $(this).removeClass('autogenerated-title');
532 });
532 });
533
533
534 % if c.default_source_ref:
534 % if c.default_source_ref:
535 // in case we have a pre-selected value, use it now
535 // in case we have a pre-selected value, use it now
536 $sourceRef.select2('val', '${c.default_source_ref}');
536 $sourceRef.select2('val', '${c.default_source_ref}');
537 // diff preview load
537 // diff preview load
538 loadRepoRefDiffPreview();
538 loadRepoRefDiffPreview();
539 // default reviewers
539 // default reviewers
540 reviewersController.loadDefaultReviewers(
540 reviewersController.loadDefaultReviewers(
541 sourceRepo(), sourceRef(), targetRepo(), targetRef());
541 sourceRepo(), sourceRef(), targetRepo(), targetRef());
542 % endif
542 % endif
543
543
544 ReviewerAutoComplete('#user');
544 ReviewerAutoComplete('#user');
545 });
545 });
546 </script>
546 </script>
547
547
548 </%def>
548 </%def>
@@ -1,152 +1,201 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.mako"/>
2 <%inherit file="/base/base.mako"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 %if c.repo_name:
5 %if c.repo_name:
6 ${_('Search inside repository %(repo_name)s') % {'repo_name': c.repo_name}}
6 ${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
7 %elif c.repo_group_name:
8 ${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
7 %else:
9 %else:
8 ${_('Search inside all accessible repositories')}
10 ${_('Search inside all accessible repositories')}
9 %endif
11 %endif
10 %if c.rhodecode_name:
12 %if c.rhodecode_name:
11 &middot; ${h.branding(c.rhodecode_name)}
13 &middot; ${h.branding(c.rhodecode_name)}
12 %endif
14 %endif
13 </%def>
15 </%def>
14
16
15 <%def name="breadcrumbs_links()">
17 <%def name="breadcrumbs_links()">
16 %if c.repo_name:
18 %if c.repo_name:
17 ${_('Search inside repository %(repo_name)s') % {'repo_name': c.repo_name}}
19 ${_('Search inside repository {repo_name}').format(repo_name=c.repo_name)}
20 %elif c.repo_group_name:
21 ${_('Search inside repository group {repo_group_name}').format(repo_group_name=c.repo_group_name)}
18 %else:
22 %else:
19 ${_('Search inside all accessible repositories')}
23 ${_('Search inside all accessible repositories')}
20 %endif
24 %endif
21
25
22 </%def>
26 </%def>
23
27
24 <%def name="menu_bar_nav()">
28 <%def name="menu_bar_nav()">
25 %if c.repo_name:
29 %if c.repo_name:
26 ${self.menu_items(active='repositories')}
30 ${self.menu_items(active='search')}
31 %elif c.repo_group_name:
32 ${self.menu_items(active='search')}
27 %else:
33 %else:
28 ${self.menu_items(active='search')}
34 ${self.menu_items(active='search')}
29 %endif
35 %endif
30 </%def>
36 </%def>
31
37
32 <%def name="menu_bar_subnav()">
38 <%def name="menu_bar_subnav()">
33 %if c.repo_name:
39 %if c.repo_name:
34 ${self.repo_menu(active='search')}
40 ${self.repo_menu(active='search')}
41 %elif c.repo_group_name:
42 ${self.repo_group_menu(active='search')}
35 %endif
43 %endif
36 </%def>
44 </%def>
37
45
38 <%def name="main()">
46 <%def name="main()">
39 <div class="box">
47 <div class="box">
40 %if c.repo_name:
48 %if c.repo_name:
41 <!-- box / title -->
49 <!-- box / title -->
42 <div class="title">
50 <div class="title">
43 ${self.repo_page_title(c.rhodecode_db_repo)}
51 ${self.repo_page_title(c.rhodecode_db_repo)}
44 </div>
52 </div>
45 ${h.form(h.route_path('search_repo',repo_name=c.repo_name),method='get')}
53 ${h.form(h.route_path('search_repo',repo_name=c.repo_name),method='get')}
54 %elif c.repo_group_name:
55 <!-- box / title -->
56 <div class="title">
57 ${self.repo_group_page_title(c.repo_group)}
58 </div>
59 ${h.form(h.route_path('search_repo_group',repo_group_name=c.repo_group_name),method='get')}
46 %else:
60 %else:
47 <!-- box / title -->
61 <!-- box / title -->
48 <div class="title">
62 <div class="title">
49 ${self.breadcrumbs()}
63 ${self.breadcrumbs()}
50 <ul class="links">&nbsp;</ul>
64 <ul class="links">&nbsp;</ul>
51 </div>
65 </div>
52 <!-- end box / title -->
66 <!-- end box / title -->
53 ${h.form(h.route_path('search'), method='get')}
67 ${h.form(h.route_path('search'), method='get')}
54 %endif
68 %endif
55 <div class="form search-form">
69 <div class="form search-form">
56 <div class="fields">
70 <div class="fields">
71
57 ${h.text('q', c.cur_query, placeholder="Enter query...")}
72 ${h.text('q', c.cur_query, placeholder="Enter query...")}
58
73
59 ${h.select('type',c.search_type,[('content',_('Files')), ('path',_('File path')),('commit',_('Commits'))],id='id_search_type')}
74 ${h.select('type',c.search_type,[('content',_('Files')), ('path',_('File path')),('commit',_('Commits'))],id='id_search_type')}
60 ${h.hidden('max_lines', '10')}
75 ${h.hidden('max_lines', '10')}
76
61 <input type="submit" value="${_('Search')}" class="btn"/>
77 <input type="submit" value="${_('Search')}" class="btn"/>
62 <br/>
78 <br/>
63
79
80 <div class="search-tags">
81 %if c.repo_name:
82 <span class="tag tag-ok disabled">
83 %if h.is_hg(c.rhodecode_db_repo):
84 <i class="icon-hg"></i>
85 %endif
86 %if h.is_git(c.rhodecode_db_repo):
87 <i class="icon-git"></i>
88 %endif
89 %if h.is_svn(c.rhodecode_db_repo):
90 <i class="icon-svn"></i>
91 %endif
92 ${c.repo_name}
93 </span>
94
95 %elif c.repo_group_name:
96 <span class="tag tag-ok disabled">
97 <i class="icon-folder-close"></i>
98 ${c.repo_group_name}
99 </span>
100 %endif
101
102 % for search_tag in c.search_tags:
103 <span class="tag tag-ok disabled">${search_tag}</span>
104 % endfor
105
106 </div>
107
64 <div class="search-feedback-items">
108 <div class="search-feedback-items">
65 % for error in c.errors:
109 % for error in c.errors:
66 <span class="error-message">
110 <span class="error-message">
67 % for k,v in error.asdict().items():
111 % for k,v in error.asdict().items():
68 ${k} - ${v}
112 ${k} - ${v}
69 % endfor
113 % endfor
70 </span>
114 </span>
71 % endfor
115 % endfor
72 <div class="field">
116 <div class="field">
73 <p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Query Langague examples')}</p>
117 <p class="filterexample" style="position: inherit" onclick="$('#search-help').toggle()">${_('Query Langague examples')}</p>
74 <pre id="search-help" style="display: none">\
118 <pre id="search-help" style="display: none">\
75
119
76 % if c.searcher.name == 'whoosh':
120 % if c.searcher.name == 'whoosh':
77 Example filter terms for `Whoosh` search:
121 Example filter terms for `Whoosh` search:
78 query lang: <a href="${c.searcher.query_lang_doc}">Whoosh Query Language</a>
122 query lang: <a href="${c.searcher.query_lang_doc}">Whoosh Query Language</a>
79 Whoosh has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
123 Whoosh has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
80
124
81 Generate wildcards using '*' character:
125 Generate wildcards using '*' character:
82 "repo_name:vcs*" - search everything starting with 'vcs'
126 "repo_name:vcs*" - search everything starting with 'vcs'
83 "repo_name:*vcs*" - search for repository containing 'vcs'
127 "repo_name:*vcs*" - search for repository containing 'vcs'
84
128
85 Optional AND / OR operators in queries
129 Optional AND / OR operators in queries
86 "repo_name:vcs OR repo_name:test"
130 "repo_name:vcs OR repo_name:test"
87 "owner:test AND repo_name:test*" AND extension:py
131 "owner:test AND repo_name:test*" AND extension:py
88
132
89 Move advanced search is available via ElasticSearch6 backend in EE edition.
133 Move advanced search is available via ElasticSearch6 backend in EE edition.
90 % elif c.searcher.name == 'elasticsearch' and c.searcher.es_version == '2':
134 % elif c.searcher.name == 'elasticsearch' and c.searcher.es_version == '2':
91 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
135 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
92 ElasticSearch-2 has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
136 ElasticSearch-2 has limited query capabilities. For advanced search use ElasticSearch 6 from RhodeCode EE edition.
93
137
94 search type: content (File Content)
138 search type: content (File Content)
95 indexed fields: content
139 indexed fields: content
96
140
97 # search for `fix` string in all files
141 # search for `fix` string in all files
98 fix
142 fix
99
143
100 search type: commit (Commit message)
144 search type: commit (Commit message)
101 indexed fields: message
145 indexed fields: message
102
146
103 search type: path (File name)
147 search type: path (File name)
104 indexed fields: path
148 indexed fields: path
105
149
106 % else:
150 % else:
107 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
151 Example filter terms for `ElasticSearch-${c.searcher.es_version}`search:
108 query lang: <a href="${c.searcher.query_lang_doc}">ES 6 Query Language</a>
152 query lang: <a href="${c.searcher.query_lang_doc}">ES 6 Query Language</a>
109 The reserved characters needed espace by `\`: + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /
153 The reserved characters needed espace by `\`: + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /
110 % for handler in c.searcher.get_handlers().values():
154 % for handler in c.searcher.get_handlers().values():
111
155
112 search type: ${handler.search_type_label}
156 search type: ${handler.search_type_label}
113 *indexed fields*: ${', '.join( [('\n ' if x[0]%4==0 else '')+x[1] for x in enumerate(handler.es_6_field_names)])}
157 *indexed fields*: ${', '.join( [('\n ' if x[0]%4==0 else '')+x[1] for x in enumerate(handler.es_6_field_names)])}
114 % for entry in handler.es_6_example_queries:
158 % for entry in handler.es_6_example_queries:
115 ${entry.rstrip()}
159 ${entry.rstrip()}
116 % endfor
160 % endfor
117 % endfor
161 % endfor
118
162
119 % endif
163 % endif
120 </pre>
164 </pre>
121 </div>
165 </div>
122
166
123 <div class="field">${c.runtime}</div>
167 <div class="field">${c.runtime}</div>
124 </div>
168 </div>
125 </div>
169 </div>
126 </div>
170 </div>
127
171
128 ${h.end_form()}
172 ${h.end_form()}
129 <div class="search">
173 <div class="search">
130 % if c.search_type == 'content':
174 % if c.search_type == 'content':
131 <%include file='search_content.mako'/>
175 <%include file='search_content.mako'/>
132 % elif c.search_type == 'path':
176 % elif c.search_type == 'path':
133 <%include file='search_path.mako'/>
177 <%include file='search_path.mako'/>
134 % elif c.search_type == 'commit':
178 % elif c.search_type == 'commit':
135 <%include file='search_commit.mako'/>
179 <%include file='search_commit.mako'/>
136 % elif c.search_type == 'repository':
180 % elif c.search_type == 'repository':
137 <%include file='search_repository.mako'/>
181 <%include file='search_repository.mako'/>
138 % endif
182 % endif
139 </div>
183 </div>
140 </div>
184 </div>
141 <script>
185 <script>
142 $(document).ready(function(){
186 $(document).ready(function(){
143 $('#q').autoGrowInput();
144 $("#id_search_type").select2({
187 $("#id_search_type").select2({
145 'containerCssClass': "drop-menu",
188 'containerCssClass': "drop-menu",
146 'dropdownCssClass': "drop-menu-dropdown",
189 'dropdownCssClass': "drop-menu-dropdown",
147 'dropdownAutoWidth': true,
190 'dropdownAutoWidth': true,
148 'minimumResultsForSearch': -1
191 'minimumResultsForSearch': -1
149 });
192 });
193
194 $('#q').autoGrowInput({maxWidth: 920});
195
196 setTimeout(function() {
197 $('#q').keyup()
198 }, 1);
150 })
199 })
151 </script>
200 </script>
152 </%def>
201 </%def>
@@ -1,125 +1,128 b''
1 <%inherit file="/summary/summary_base.mako"/>
1 <%inherit file="/summary/summary_base.mako"/>
2
2
3 <%namespace name="components" file="/summary/components.mako"/>
3 <%namespace name="components" file="/summary/components.mako"/>
4
4
5
5
6 <%def name="menu_bar_subnav()">
6 <%def name="menu_bar_subnav()">
7 ${self.repo_menu(active='summary')}
7 ${self.repo_menu(active='summary')}
8 </%def>
8 </%def>
9
9
10 <%def name="main()">
10 <%def name="main()">
11
11
12 <div class="title">
12 <div class="title">
13 ${self.repo_page_title(c.rhodecode_db_repo)}
13 ${self.repo_page_title(c.rhodecode_db_repo)}
14 <ul class="links icon-only-links block-right">
14 ## Context Action
15 <li>
15 <div>
16 %if c.rhodecode_user.username != h.DEFAULT_USER:
16 <ul class="links icon-only-links block-right">
17 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}"><i class="icon-rss-sign"></i></a>
17 <li>
18 %else:
18 %if c.rhodecode_user.username != h.DEFAULT_USER:
19 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name)}" title="${_('RSS Feed')}"><i class="icon-rss-sign"></i></a>
19 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name, _query=dict(auth_token=c.rhodecode_user.feed_token))}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
20 %endif
20 %else:
21 </li>
21 <a href="${h.route_path('atom_feed_home', repo_name=c.rhodecode_db_repo.repo_name)}" title="${_('RSS Feed')}" class="btn btn-sm"><i class="icon-rss-sign"></i>RSS</a>
22 </ul>
22 %endif
23 </li>
24 </ul>
25 </div>
23 </div>
26 </div>
24
27
25 <div id="repo-summary" class="summary">
28 <div id="repo-summary" class="summary">
26 ${components.summary_detail(breadcrumbs_links=self.breadcrumbs_links(), show_downloads=True)}
29 ${components.summary_detail(breadcrumbs_links=self.breadcrumbs_links(), show_downloads=True)}
27 ${components.summary_stats(gravatar_function=self.gravatar_with_user)}
30 ${components.summary_stats(gravatar_function=self.gravatar_with_user)}
28 </div><!--end repo-summary-->
31 </div><!--end repo-summary-->
29
32
30
33
31 <div class="box" >
34 <div class="box" >
32 %if not c.repo_commits:
35 %if not c.repo_commits:
33 <div class="title">
36 <div class="title">
34 <h3>${_('Quick start')}</h3>
37 <h3>${_('Quick start')}</h3>
35 </div>
38 </div>
36 %endif
39 %endif
37 <div class="table">
40 <div class="table">
38 <div id="shortlog_data">
41 <div id="shortlog_data">
39 <%include file='summary_commits.mako'/>
42 <%include file='summary_commits.mako'/>
40 </div>
43 </div>
41 </div>
44 </div>
42 </div>
45 </div>
43
46
44 %if c.readme_data:
47 %if c.readme_data:
45 <div id="readme" class="anchor">
48 <div id="readme" class="anchor">
46 <div class="box" >
49 <div class="box" >
47 <div class="title" title="${h.tooltip(_('Readme file from commit %s:%s') % (c.rhodecode_db_repo.landing_rev[0], c.rhodecode_db_repo.landing_rev[1]))}">
50 <div class="title" title="${h.tooltip(_('Readme file from commit %s:%s') % (c.rhodecode_db_repo.landing_rev[0], c.rhodecode_db_repo.landing_rev[1]))}">
48 <h3 class="breadcrumbs">
51 <h3 class="breadcrumbs">
49 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.landing_rev[1],f_path=c.readme_file)}">${c.readme_file}</a>
52 <a href="${h.route_path('repo_files',repo_name=c.repo_name,commit_id=c.rhodecode_db_repo.landing_rev[1],f_path=c.readme_file)}">${c.readme_file}</a>
50 </h3>
53 </h3>
51 </div>
54 </div>
52 <div class="readme codeblock">
55 <div class="readme codeblock">
53 <div class="readme_box">
56 <div class="readme_box">
54 ${c.readme_data|n}
57 ${c.readme_data|n}
55 </div>
58 </div>
56 </div>
59 </div>
57 </div>
60 </div>
58 </div>
61 </div>
59 %endif
62 %endif
60
63
61 <script type="text/javascript">
64 <script type="text/javascript">
62 $(document).ready(function(){
65 $(document).ready(function(){
63
66
64 var showCloneField = function(clone_url_format){
67 var showCloneField = function(clone_url_format){
65 $.each(['http', 'http_id', 'ssh'], function (idx, val) {
68 $.each(['http', 'http_id', 'ssh'], function (idx, val) {
66 if(val === clone_url_format){
69 if(val === clone_url_format){
67 $('#clone_option_' + val).show();
70 $('#clone_option_' + val).show();
68 $('#clone_option').val(val)
71 $('#clone_option').val(val)
69 } else {
72 } else {
70 $('#clone_option_' + val).hide();
73 $('#clone_option_' + val).hide();
71 }
74 }
72 });
75 });
73 };
76 };
74 // default taken from session
77 // default taken from session
75 showCloneField(templateContext.session_attrs.clone_url_format);
78 showCloneField(templateContext.session_attrs.clone_url_format);
76
79
77 $('#clone_option').on('change', function(e) {
80 $('#clone_option').on('change', function(e) {
78 var selected = $(this).val();
81 var selected = $(this).val();
79
82
80 storeUserSessionAttr('rc_user_session_attr.clone_url_format', selected);
83 storeUserSessionAttr('rc_user_session_attr.clone_url_format', selected);
81 showCloneField(selected)
84 showCloneField(selected)
82 });
85 });
83
86
84 var initialCommitData = {
87 var initialCommitData = {
85 id: null,
88 id: null,
86 text: 'tip',
89 text: 'tip',
87 type: 'tag',
90 type: 'tag',
88 raw_id: null,
91 raw_id: null,
89 files_url: null
92 files_url: null
90 };
93 };
91
94
92 select2RefSwitcher('#download_options', initialCommitData);
95 select2RefSwitcher('#download_options', initialCommitData);
93
96
94 // on change of download options
97 // on change of download options
95 $('#download_options').on('change', function(e) {
98 $('#download_options').on('change', function(e) {
96 // format of Object {text: "v0.0.3", type: "tag", id: "rev"}
99 // format of Object {text: "v0.0.3", type: "tag", id: "rev"}
97 var ext = '.zip';
100 var ext = '.zip';
98 var selected_cs = e.added;
101 var selected_cs = e.added;
99 var fname = e.added.raw_id + ext;
102 var fname = e.added.raw_id + ext;
100 var href = pyroutes.url('repo_archivefile', {'repo_name': templateContext.repo_name, 'fname':fname});
103 var href = pyroutes.url('repo_archivefile', {'repo_name': templateContext.repo_name, 'fname':fname});
101 // set new label
104 // set new label
102 $('#archive_link').html('<i class="icon-archive"></i> {0}{1}'.format(escapeHtml(e.added.text), ext));
105 $('#archive_link').html('<i class="icon-archive"></i> {0}{1}'.format(escapeHtml(e.added.text), ext));
103
106
104 // set new url to button,
107 // set new url to button,
105 $('#archive_link').attr('href', href)
108 $('#archive_link').attr('href', href)
106 });
109 });
107
110
108
111
109 // calculate size of repository
112 // calculate size of repository
110 calculateSize = function () {
113 calculateSize = function () {
111
114
112 var callback = function (data) {
115 var callback = function (data) {
113 % if c.show_stats:
116 % if c.show_stats:
114 showRepoStats('lang_stats', data);
117 showRepoStats('lang_stats', data);
115 % endif
118 % endif
116 };
119 };
117
120
118 showRepoSize('repo_size_container', templateContext.repo_name, templateContext.repo_landing_commit, callback);
121 showRepoSize('repo_size_container', templateContext.repo_name, templateContext.repo_landing_commit, callback);
119
122
120 }
123 }
121
124
122 })
125 })
123 </script>
126 </script>
124
127
125 </%def>
128 </%def>
General Comments 0
You need to be logged in to leave comments. Login now