##// END OF EJS Templates
search: fixed UI of search items after redesign.
marcink -
r3745:41ddf1f6 new-ui
parent child Browse files
Show More
@@ -1,2071 +1,2075 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
202
203 get_error = _GetError()
203 get_error = _GetError()
204
204
205
205
206 class _ToolTip(object):
206 class _ToolTip(object):
207
207
208 def __call__(self, tooltip_title, trim_at=50):
208 def __call__(self, tooltip_title, trim_at=50):
209 """
209 """
210 Special function just to wrap our text into nice formatted
210 Special function just to wrap our text into nice formatted
211 autowrapped text
211 autowrapped text
212
212
213 :param tooltip_title:
213 :param tooltip_title:
214 """
214 """
215 tooltip_title = escape(tooltip_title)
215 tooltip_title = escape(tooltip_title)
216 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
216 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
217 return tooltip_title
217 return tooltip_title
218
218
219
219
220 tooltip = _ToolTip()
220 tooltip = _ToolTip()
221
221
222 files_icon = icon = '<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy the full path"></i>'
222 files_icon = icon = '<i class="file-breadcrumb-copy tooltip icon-clipboard clipboard-action" data-clipboard-text="{}" title="Copy the full path"></i>'
223
223
224 def files_breadcrumbs(repo_name, commit_id, file_path, at_ref=None, limit_items=False):
224
225 def files_breadcrumbs(repo_name, commit_id, file_path, at_ref=None, limit_items=False, linkify_last_item=False):
225 if isinstance(file_path, str):
226 if isinstance(file_path, str):
226 file_path = safe_unicode(file_path)
227 file_path = safe_unicode(file_path)
227
228
228 route_qry = {'at': at_ref} if at_ref else None
229 route_qry = {'at': at_ref} if at_ref else None
229
230
230 # first segment is a `..` link to repo files
231 # first segment is a `..` link to repo files
231 root_name = literal(u'<i class="icon-home"></i>')
232 root_name = literal(u'<i class="icon-home"></i>')
232 url_segments = [
233 url_segments = [
233 link_to(
234 link_to(
234 root_name,
235 root_name,
235 route_path(
236 route_path(
236 'repo_files',
237 'repo_files',
237 repo_name=repo_name,
238 repo_name=repo_name,
238 commit_id=commit_id,
239 commit_id=commit_id,
239 f_path='',
240 f_path='',
240 _query=route_qry),
241 _query=route_qry),
241 )]
242 )]
242
243
243 path_segments = file_path.split('/')
244 path_segments = file_path.split('/')
244 last_cnt = len(path_segments) - 1
245 last_cnt = len(path_segments) - 1
245 for cnt, segment in enumerate(path_segments):
246 for cnt, segment in enumerate(path_segments):
246 if not segment:
247 if not segment:
247 continue
248 continue
248 segment_html = escape(segment)
249 segment_html = escape(segment)
249
250
250 if cnt != last_cnt:
251 last_item = cnt == last_cnt
252
253 if last_item and linkify_last_item is False:
254 # plain version
255 url_segments.append(segment_html)
256 else:
251 url_segments.append(
257 url_segments.append(
252 link_to(
258 link_to(
253 segment_html,
259 segment_html,
254 route_path(
260 route_path(
255 'repo_files',
261 'repo_files',
256 repo_name=repo_name,
262 repo_name=repo_name,
257 commit_id=commit_id,
263 commit_id=commit_id,
258 f_path='/'.join(path_segments[:cnt + 1]),
264 f_path='/'.join(path_segments[:cnt + 1]),
259 _query=route_qry),
265 _query=route_qry),
260 ))
266 ))
261 else:
262 url_segments.append(segment_html)
263
267
264 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
268 limited_url_segments = url_segments[:1] + ['...'] + url_segments[-5:]
265 if limit_items and len(limited_url_segments) < len(url_segments):
269 if limit_items and len(limited_url_segments) < len(url_segments):
266 url_segments = limited_url_segments
270 url_segments = limited_url_segments
267
271
268 full_path = file_path
272 full_path = file_path
269 icon = files_icon.format(escape(full_path))
273 icon = files_icon.format(escape(full_path))
270 if file_path == '':
274 if file_path == '':
271 return root_name
275 return root_name
272 else:
276 else:
273 return literal(' / '.join(url_segments) + icon)
277 return literal(' / '.join(url_segments) + icon)
274
278
275
279
276 def files_url_data(request):
280 def files_url_data(request):
277 matchdict = request.matchdict
281 matchdict = request.matchdict
278
282
279 if 'f_path' not in matchdict:
283 if 'f_path' not in matchdict:
280 matchdict['f_path'] = ''
284 matchdict['f_path'] = ''
281
285
282 if 'commit_id' not in matchdict:
286 if 'commit_id' not in matchdict:
283 matchdict['commit_id'] = 'tip'
287 matchdict['commit_id'] = 'tip'
284
288
285 return json.dumps(matchdict)
289 return json.dumps(matchdict)
286
290
287
291
288 def code_highlight(code, lexer, formatter, use_hl_filter=False):
292 def code_highlight(code, lexer, formatter, use_hl_filter=False):
289 """
293 """
290 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
294 Lex ``code`` with ``lexer`` and format it with the formatter ``formatter``.
291
295
292 If ``outfile`` is given and a valid file object (an object
296 If ``outfile`` is given and a valid file object (an object
293 with a ``write`` method), the result will be written to it, otherwise
297 with a ``write`` method), the result will be written to it, otherwise
294 it is returned as a string.
298 it is returned as a string.
295 """
299 """
296 if use_hl_filter:
300 if use_hl_filter:
297 # add HL filter
301 # add HL filter
298 from rhodecode.lib.index import search_utils
302 from rhodecode.lib.index import search_utils
299 lexer.add_filter(search_utils.ElasticSearchHLFilter())
303 lexer.add_filter(search_utils.ElasticSearchHLFilter())
300 return pygments.format(pygments.lex(code, lexer), formatter)
304 return pygments.format(pygments.lex(code, lexer), formatter)
301
305
302
306
303 class CodeHtmlFormatter(HtmlFormatter):
307 class CodeHtmlFormatter(HtmlFormatter):
304 """
308 """
305 My code Html Formatter for source codes
309 My code Html Formatter for source codes
306 """
310 """
307
311
308 def wrap(self, source, outfile):
312 def wrap(self, source, outfile):
309 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
313 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
310
314
311 def _wrap_code(self, source):
315 def _wrap_code(self, source):
312 for cnt, it in enumerate(source):
316 for cnt, it in enumerate(source):
313 i, t = it
317 i, t = it
314 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
318 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
315 yield i, t
319 yield i, t
316
320
317 def _wrap_tablelinenos(self, inner):
321 def _wrap_tablelinenos(self, inner):
318 dummyoutfile = StringIO.StringIO()
322 dummyoutfile = StringIO.StringIO()
319 lncount = 0
323 lncount = 0
320 for t, line in inner:
324 for t, line in inner:
321 if t:
325 if t:
322 lncount += 1
326 lncount += 1
323 dummyoutfile.write(line)
327 dummyoutfile.write(line)
324
328
325 fl = self.linenostart
329 fl = self.linenostart
326 mw = len(str(lncount + fl - 1))
330 mw = len(str(lncount + fl - 1))
327 sp = self.linenospecial
331 sp = self.linenospecial
328 st = self.linenostep
332 st = self.linenostep
329 la = self.lineanchors
333 la = self.lineanchors
330 aln = self.anchorlinenos
334 aln = self.anchorlinenos
331 nocls = self.noclasses
335 nocls = self.noclasses
332 if sp:
336 if sp:
333 lines = []
337 lines = []
334
338
335 for i in range(fl, fl + lncount):
339 for i in range(fl, fl + lncount):
336 if i % st == 0:
340 if i % st == 0:
337 if i % sp == 0:
341 if i % sp == 0:
338 if aln:
342 if aln:
339 lines.append('<a href="#%s%d" class="special">%*d</a>' %
343 lines.append('<a href="#%s%d" class="special">%*d</a>' %
340 (la, i, mw, i))
344 (la, i, mw, i))
341 else:
345 else:
342 lines.append('<span class="special">%*d</span>' % (mw, i))
346 lines.append('<span class="special">%*d</span>' % (mw, i))
343 else:
347 else:
344 if aln:
348 if aln:
345 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
349 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
346 else:
350 else:
347 lines.append('%*d' % (mw, i))
351 lines.append('%*d' % (mw, i))
348 else:
352 else:
349 lines.append('')
353 lines.append('')
350 ls = '\n'.join(lines)
354 ls = '\n'.join(lines)
351 else:
355 else:
352 lines = []
356 lines = []
353 for i in range(fl, fl + lncount):
357 for i in range(fl, fl + lncount):
354 if i % st == 0:
358 if i % st == 0:
355 if aln:
359 if aln:
356 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
360 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
357 else:
361 else:
358 lines.append('%*d' % (mw, i))
362 lines.append('%*d' % (mw, i))
359 else:
363 else:
360 lines.append('')
364 lines.append('')
361 ls = '\n'.join(lines)
365 ls = '\n'.join(lines)
362
366
363 # in case you wonder about the seemingly redundant <div> here: since the
367 # in case you wonder about the seemingly redundant <div> here: since the
364 # content in the other cell also is wrapped in a div, some browsers in
368 # content in the other cell also is wrapped in a div, some browsers in
365 # some configurations seem to mess up the formatting...
369 # some configurations seem to mess up the formatting...
366 if nocls:
370 if nocls:
367 yield 0, ('<table class="%stable">' % self.cssclass +
371 yield 0, ('<table class="%stable">' % self.cssclass +
368 '<tr><td><div class="linenodiv" '
372 '<tr><td><div class="linenodiv" '
369 'style="background-color: #f0f0f0; padding-right: 10px">'
373 'style="background-color: #f0f0f0; padding-right: 10px">'
370 '<pre style="line-height: 125%">' +
374 '<pre style="line-height: 125%">' +
371 ls + '</pre></div></td><td id="hlcode" class="code">')
375 ls + '</pre></div></td><td id="hlcode" class="code">')
372 else:
376 else:
373 yield 0, ('<table class="%stable">' % self.cssclass +
377 yield 0, ('<table class="%stable">' % self.cssclass +
374 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
378 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
375 ls + '</pre></div></td><td id="hlcode" class="code">')
379 ls + '</pre></div></td><td id="hlcode" class="code">')
376 yield 0, dummyoutfile.getvalue()
380 yield 0, dummyoutfile.getvalue()
377 yield 0, '</td></tr></table>'
381 yield 0, '</td></tr></table>'
378
382
379
383
380 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
384 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
381 def __init__(self, **kw):
385 def __init__(self, **kw):
382 # only show these line numbers if set
386 # only show these line numbers if set
383 self.only_lines = kw.pop('only_line_numbers', [])
387 self.only_lines = kw.pop('only_line_numbers', [])
384 self.query_terms = kw.pop('query_terms', [])
388 self.query_terms = kw.pop('query_terms', [])
385 self.max_lines = kw.pop('max_lines', 5)
389 self.max_lines = kw.pop('max_lines', 5)
386 self.line_context = kw.pop('line_context', 3)
390 self.line_context = kw.pop('line_context', 3)
387 self.url = kw.pop('url', None)
391 self.url = kw.pop('url', None)
388
392
389 super(CodeHtmlFormatter, self).__init__(**kw)
393 super(CodeHtmlFormatter, self).__init__(**kw)
390
394
391 def _wrap_code(self, source):
395 def _wrap_code(self, source):
392 for cnt, it in enumerate(source):
396 for cnt, it in enumerate(source):
393 i, t = it
397 i, t = it
394 t = '<pre>%s</pre>' % t
398 t = '<pre>%s</pre>' % t
395 yield i, t
399 yield i, t
396
400
397 def _wrap_tablelinenos(self, inner):
401 def _wrap_tablelinenos(self, inner):
398 yield 0, '<table class="code-highlight %stable">' % self.cssclass
402 yield 0, '<table class="code-highlight %stable">' % self.cssclass
399
403
400 last_shown_line_number = 0
404 last_shown_line_number = 0
401 current_line_number = 1
405 current_line_number = 1
402
406
403 for t, line in inner:
407 for t, line in inner:
404 if not t:
408 if not t:
405 yield t, line
409 yield t, line
406 continue
410 continue
407
411
408 if current_line_number in self.only_lines:
412 if current_line_number in self.only_lines:
409 if last_shown_line_number + 1 != current_line_number:
413 if last_shown_line_number + 1 != current_line_number:
410 yield 0, '<tr>'
414 yield 0, '<tr>'
411 yield 0, '<td class="line">...</td>'
415 yield 0, '<td class="line">...</td>'
412 yield 0, '<td id="hlcode" class="code"></td>'
416 yield 0, '<td id="hlcode" class="code"></td>'
413 yield 0, '</tr>'
417 yield 0, '</tr>'
414
418
415 yield 0, '<tr>'
419 yield 0, '<tr>'
416 if self.url:
420 if self.url:
417 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
421 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
418 self.url, current_line_number, current_line_number)
422 self.url, current_line_number, current_line_number)
419 else:
423 else:
420 yield 0, '<td class="line"><a href="">%i</a></td>' % (
424 yield 0, '<td class="line"><a href="">%i</a></td>' % (
421 current_line_number)
425 current_line_number)
422 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
426 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
423 yield 0, '</tr>'
427 yield 0, '</tr>'
424
428
425 last_shown_line_number = current_line_number
429 last_shown_line_number = current_line_number
426
430
427 current_line_number += 1
431 current_line_number += 1
428
432
429 yield 0, '</table>'
433 yield 0, '</table>'
430
434
431
435
432 def hsv_to_rgb(h, s, v):
436 def hsv_to_rgb(h, s, v):
433 """ Convert hsv color values to rgb """
437 """ Convert hsv color values to rgb """
434
438
435 if s == 0.0:
439 if s == 0.0:
436 return v, v, v
440 return v, v, v
437 i = int(h * 6.0) # XXX assume int() truncates!
441 i = int(h * 6.0) # XXX assume int() truncates!
438 f = (h * 6.0) - i
442 f = (h * 6.0) - i
439 p = v * (1.0 - s)
443 p = v * (1.0 - s)
440 q = v * (1.0 - s * f)
444 q = v * (1.0 - s * f)
441 t = v * (1.0 - s * (1.0 - f))
445 t = v * (1.0 - s * (1.0 - f))
442 i = i % 6
446 i = i % 6
443 if i == 0:
447 if i == 0:
444 return v, t, p
448 return v, t, p
445 if i == 1:
449 if i == 1:
446 return q, v, p
450 return q, v, p
447 if i == 2:
451 if i == 2:
448 return p, v, t
452 return p, v, t
449 if i == 3:
453 if i == 3:
450 return p, q, v
454 return p, q, v
451 if i == 4:
455 if i == 4:
452 return t, p, v
456 return t, p, v
453 if i == 5:
457 if i == 5:
454 return v, p, q
458 return v, p, q
455
459
456
460
457 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
461 def unique_color_generator(n=10000, saturation=0.10, lightness=0.95):
458 """
462 """
459 Generator for getting n of evenly distributed colors using
463 Generator for getting n of evenly distributed colors using
460 hsv color and golden ratio. It always return same order of colors
464 hsv color and golden ratio. It always return same order of colors
461
465
462 :param n: number of colors to generate
466 :param n: number of colors to generate
463 :param saturation: saturation of returned colors
467 :param saturation: saturation of returned colors
464 :param lightness: lightness of returned colors
468 :param lightness: lightness of returned colors
465 :returns: RGB tuple
469 :returns: RGB tuple
466 """
470 """
467
471
468 golden_ratio = 0.618033988749895
472 golden_ratio = 0.618033988749895
469 h = 0.22717784590367374
473 h = 0.22717784590367374
470
474
471 for _ in xrange(n):
475 for _ in xrange(n):
472 h += golden_ratio
476 h += golden_ratio
473 h %= 1
477 h %= 1
474 HSV_tuple = [h, saturation, lightness]
478 HSV_tuple = [h, saturation, lightness]
475 RGB_tuple = hsv_to_rgb(*HSV_tuple)
479 RGB_tuple = hsv_to_rgb(*HSV_tuple)
476 yield map(lambda x: str(int(x * 256)), RGB_tuple)
480 yield map(lambda x: str(int(x * 256)), RGB_tuple)
477
481
478
482
479 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
483 def color_hasher(n=10000, saturation=0.10, lightness=0.95):
480 """
484 """
481 Returns a function which when called with an argument returns a unique
485 Returns a function which when called with an argument returns a unique
482 color for that argument, eg.
486 color for that argument, eg.
483
487
484 :param n: number of colors to generate
488 :param n: number of colors to generate
485 :param saturation: saturation of returned colors
489 :param saturation: saturation of returned colors
486 :param lightness: lightness of returned colors
490 :param lightness: lightness of returned colors
487 :returns: css RGB string
491 :returns: css RGB string
488
492
489 >>> color_hash = color_hasher()
493 >>> color_hash = color_hasher()
490 >>> color_hash('hello')
494 >>> color_hash('hello')
491 'rgb(34, 12, 59)'
495 'rgb(34, 12, 59)'
492 >>> color_hash('hello')
496 >>> color_hash('hello')
493 'rgb(34, 12, 59)'
497 'rgb(34, 12, 59)'
494 >>> color_hash('other')
498 >>> color_hash('other')
495 'rgb(90, 224, 159)'
499 'rgb(90, 224, 159)'
496 """
500 """
497
501
498 color_dict = {}
502 color_dict = {}
499 cgenerator = unique_color_generator(
503 cgenerator = unique_color_generator(
500 saturation=saturation, lightness=lightness)
504 saturation=saturation, lightness=lightness)
501
505
502 def get_color_string(thing):
506 def get_color_string(thing):
503 if thing in color_dict:
507 if thing in color_dict:
504 col = color_dict[thing]
508 col = color_dict[thing]
505 else:
509 else:
506 col = color_dict[thing] = cgenerator.next()
510 col = color_dict[thing] = cgenerator.next()
507 return "rgb(%s)" % (', '.join(col))
511 return "rgb(%s)" % (', '.join(col))
508
512
509 return get_color_string
513 return get_color_string
510
514
511
515
512 def get_lexer_safe(mimetype=None, filepath=None):
516 def get_lexer_safe(mimetype=None, filepath=None):
513 """
517 """
514 Tries to return a relevant pygments lexer using mimetype/filepath name,
518 Tries to return a relevant pygments lexer using mimetype/filepath name,
515 defaulting to plain text if none could be found
519 defaulting to plain text if none could be found
516 """
520 """
517 lexer = None
521 lexer = None
518 try:
522 try:
519 if mimetype:
523 if mimetype:
520 lexer = get_lexer_for_mimetype(mimetype)
524 lexer = get_lexer_for_mimetype(mimetype)
521 if not lexer:
525 if not lexer:
522 lexer = get_lexer_for_filename(filepath)
526 lexer = get_lexer_for_filename(filepath)
523 except pygments.util.ClassNotFound:
527 except pygments.util.ClassNotFound:
524 pass
528 pass
525
529
526 if not lexer:
530 if not lexer:
527 lexer = get_lexer_by_name('text')
531 lexer = get_lexer_by_name('text')
528
532
529 return lexer
533 return lexer
530
534
531
535
532 def get_lexer_for_filenode(filenode):
536 def get_lexer_for_filenode(filenode):
533 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
537 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
534 return lexer
538 return lexer
535
539
536
540
537 def pygmentize(filenode, **kwargs):
541 def pygmentize(filenode, **kwargs):
538 """
542 """
539 pygmentize function using pygments
543 pygmentize function using pygments
540
544
541 :param filenode:
545 :param filenode:
542 """
546 """
543 lexer = get_lexer_for_filenode(filenode)
547 lexer = get_lexer_for_filenode(filenode)
544 return literal(code_highlight(filenode.content, lexer,
548 return literal(code_highlight(filenode.content, lexer,
545 CodeHtmlFormatter(**kwargs)))
549 CodeHtmlFormatter(**kwargs)))
546
550
547
551
548 def is_following_repo(repo_name, user_id):
552 def is_following_repo(repo_name, user_id):
549 from rhodecode.model.scm import ScmModel
553 from rhodecode.model.scm import ScmModel
550 return ScmModel().is_following_repo(repo_name, user_id)
554 return ScmModel().is_following_repo(repo_name, user_id)
551
555
552
556
553 class _Message(object):
557 class _Message(object):
554 """A message returned by ``Flash.pop_messages()``.
558 """A message returned by ``Flash.pop_messages()``.
555
559
556 Converting the message to a string returns the message text. Instances
560 Converting the message to a string returns the message text. Instances
557 also have the following attributes:
561 also have the following attributes:
558
562
559 * ``message``: the message text.
563 * ``message``: the message text.
560 * ``category``: the category specified when the message was created.
564 * ``category``: the category specified when the message was created.
561 """
565 """
562
566
563 def __init__(self, category, message):
567 def __init__(self, category, message):
564 self.category = category
568 self.category = category
565 self.message = message
569 self.message = message
566
570
567 def __str__(self):
571 def __str__(self):
568 return self.message
572 return self.message
569
573
570 __unicode__ = __str__
574 __unicode__ = __str__
571
575
572 def __html__(self):
576 def __html__(self):
573 return escape(safe_unicode(self.message))
577 return escape(safe_unicode(self.message))
574
578
575
579
576 class Flash(object):
580 class Flash(object):
577 # List of allowed categories. If None, allow any category.
581 # List of allowed categories. If None, allow any category.
578 categories = ["warning", "notice", "error", "success"]
582 categories = ["warning", "notice", "error", "success"]
579
583
580 # Default category if none is specified.
584 # Default category if none is specified.
581 default_category = "notice"
585 default_category = "notice"
582
586
583 def __init__(self, session_key="flash", categories=None,
587 def __init__(self, session_key="flash", categories=None,
584 default_category=None):
588 default_category=None):
585 """
589 """
586 Instantiate a ``Flash`` object.
590 Instantiate a ``Flash`` object.
587
591
588 ``session_key`` is the key to save the messages under in the user's
592 ``session_key`` is the key to save the messages under in the user's
589 session.
593 session.
590
594
591 ``categories`` is an optional list which overrides the default list
595 ``categories`` is an optional list which overrides the default list
592 of categories.
596 of categories.
593
597
594 ``default_category`` overrides the default category used for messages
598 ``default_category`` overrides the default category used for messages
595 when none is specified.
599 when none is specified.
596 """
600 """
597 self.session_key = session_key
601 self.session_key = session_key
598 if categories is not None:
602 if categories is not None:
599 self.categories = categories
603 self.categories = categories
600 if default_category is not None:
604 if default_category is not None:
601 self.default_category = default_category
605 self.default_category = default_category
602 if self.categories and self.default_category not in self.categories:
606 if self.categories and self.default_category not in self.categories:
603 raise ValueError(
607 raise ValueError(
604 "unrecognized default category %r" % (self.default_category,))
608 "unrecognized default category %r" % (self.default_category,))
605
609
606 def pop_messages(self, session=None, request=None):
610 def pop_messages(self, session=None, request=None):
607 """
611 """
608 Return all accumulated messages and delete them from the session.
612 Return all accumulated messages and delete them from the session.
609
613
610 The return value is a list of ``Message`` objects.
614 The return value is a list of ``Message`` objects.
611 """
615 """
612 messages = []
616 messages = []
613
617
614 if not session:
618 if not session:
615 if not request:
619 if not request:
616 request = get_current_request()
620 request = get_current_request()
617 session = request.session
621 session = request.session
618
622
619 # Pop the 'old' pylons flash messages. They are tuples of the form
623 # Pop the 'old' pylons flash messages. They are tuples of the form
620 # (category, message)
624 # (category, message)
621 for cat, msg in session.pop(self.session_key, []):
625 for cat, msg in session.pop(self.session_key, []):
622 messages.append(_Message(cat, msg))
626 messages.append(_Message(cat, msg))
623
627
624 # Pop the 'new' pyramid flash messages for each category as list
628 # Pop the 'new' pyramid flash messages for each category as list
625 # of strings.
629 # of strings.
626 for cat in self.categories:
630 for cat in self.categories:
627 for msg in session.pop_flash(queue=cat):
631 for msg in session.pop_flash(queue=cat):
628 messages.append(_Message(cat, msg))
632 messages.append(_Message(cat, msg))
629 # Map messages from the default queue to the 'notice' category.
633 # Map messages from the default queue to the 'notice' category.
630 for msg in session.pop_flash():
634 for msg in session.pop_flash():
631 messages.append(_Message('notice', msg))
635 messages.append(_Message('notice', msg))
632
636
633 session.save()
637 session.save()
634 return messages
638 return messages
635
639
636 def json_alerts(self, session=None, request=None):
640 def json_alerts(self, session=None, request=None):
637 payloads = []
641 payloads = []
638 messages = flash.pop_messages(session=session, request=request)
642 messages = flash.pop_messages(session=session, request=request)
639 if messages:
643 if messages:
640 for message in messages:
644 for message in messages:
641 subdata = {}
645 subdata = {}
642 if hasattr(message.message, 'rsplit'):
646 if hasattr(message.message, 'rsplit'):
643 flash_data = message.message.rsplit('|DELIM|', 1)
647 flash_data = message.message.rsplit('|DELIM|', 1)
644 org_message = flash_data[0]
648 org_message = flash_data[0]
645 if len(flash_data) > 1:
649 if len(flash_data) > 1:
646 subdata = json.loads(flash_data[1])
650 subdata = json.loads(flash_data[1])
647 else:
651 else:
648 org_message = message.message
652 org_message = message.message
649 payloads.append({
653 payloads.append({
650 'message': {
654 'message': {
651 'message': u'{}'.format(org_message),
655 'message': u'{}'.format(org_message),
652 'level': message.category,
656 'level': message.category,
653 'force': True,
657 'force': True,
654 'subdata': subdata
658 'subdata': subdata
655 }
659 }
656 })
660 })
657 return json.dumps(payloads)
661 return json.dumps(payloads)
658
662
659 def __call__(self, message, category=None, ignore_duplicate=False,
663 def __call__(self, message, category=None, ignore_duplicate=False,
660 session=None, request=None):
664 session=None, request=None):
661
665
662 if not session:
666 if not session:
663 if not request:
667 if not request:
664 request = get_current_request()
668 request = get_current_request()
665 session = request.session
669 session = request.session
666
670
667 session.flash(
671 session.flash(
668 message, queue=category, allow_duplicate=not ignore_duplicate)
672 message, queue=category, allow_duplicate=not ignore_duplicate)
669
673
670
674
671 flash = Flash()
675 flash = Flash()
672
676
673 #==============================================================================
677 #==============================================================================
674 # SCM FILTERS available via h.
678 # SCM FILTERS available via h.
675 #==============================================================================
679 #==============================================================================
676 from rhodecode.lib.vcs.utils import author_name, author_email
680 from rhodecode.lib.vcs.utils import author_name, author_email
677 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
681 from rhodecode.lib.utils2 import credentials_filter, age, age_from_seconds
678 from rhodecode.model.db import User, ChangesetStatus
682 from rhodecode.model.db import User, ChangesetStatus
679
683
680 capitalize = lambda x: x.capitalize()
684 capitalize = lambda x: x.capitalize()
681 email = author_email
685 email = author_email
682 short_id = lambda x: x[:12]
686 short_id = lambda x: x[:12]
683 hide_credentials = lambda x: ''.join(credentials_filter(x))
687 hide_credentials = lambda x: ''.join(credentials_filter(x))
684
688
685
689
686 import pytz
690 import pytz
687 import tzlocal
691 import tzlocal
688 local_timezone = tzlocal.get_localzone()
692 local_timezone = tzlocal.get_localzone()
689
693
690
694
691 def age_component(datetime_iso, value=None, time_is_local=False):
695 def age_component(datetime_iso, value=None, time_is_local=False):
692 title = value or format_date(datetime_iso)
696 title = value or format_date(datetime_iso)
693 tzinfo = '+00:00'
697 tzinfo = '+00:00'
694
698
695 # detect if we have a timezone info, otherwise, add it
699 # detect if we have a timezone info, otherwise, add it
696 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
700 if time_is_local and isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
697 force_timezone = os.environ.get('RC_TIMEZONE', '')
701 force_timezone = os.environ.get('RC_TIMEZONE', '')
698 if force_timezone:
702 if force_timezone:
699 force_timezone = pytz.timezone(force_timezone)
703 force_timezone = pytz.timezone(force_timezone)
700 timezone = force_timezone or local_timezone
704 timezone = force_timezone or local_timezone
701 offset = timezone.localize(datetime_iso).strftime('%z')
705 offset = timezone.localize(datetime_iso).strftime('%z')
702 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
706 tzinfo = '{}:{}'.format(offset[:-2], offset[-2:])
703
707
704 return literal(
708 return literal(
705 '<time class="timeago tooltip" '
709 '<time class="timeago tooltip" '
706 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
710 'title="{1}{2}" datetime="{0}{2}">{1}</time>'.format(
707 datetime_iso, title, tzinfo))
711 datetime_iso, title, tzinfo))
708
712
709
713
710 def _shorten_commit_id(commit_id, commit_len=None):
714 def _shorten_commit_id(commit_id, commit_len=None):
711 if commit_len is None:
715 if commit_len is None:
712 request = get_current_request()
716 request = get_current_request()
713 commit_len = request.call_context.visual.show_sha_length
717 commit_len = request.call_context.visual.show_sha_length
714 return commit_id[:commit_len]
718 return commit_id[:commit_len]
715
719
716
720
717 def show_id(commit, show_idx=None, commit_len=None):
721 def show_id(commit, show_idx=None, commit_len=None):
718 """
722 """
719 Configurable function that shows ID
723 Configurable function that shows ID
720 by default it's r123:fffeeefffeee
724 by default it's r123:fffeeefffeee
721
725
722 :param commit: commit instance
726 :param commit: commit instance
723 """
727 """
724 if show_idx is None:
728 if show_idx is None:
725 request = get_current_request()
729 request = get_current_request()
726 show_idx = request.call_context.visual.show_revision_number
730 show_idx = request.call_context.visual.show_revision_number
727
731
728 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
732 raw_id = _shorten_commit_id(commit.raw_id, commit_len=commit_len)
729 if show_idx:
733 if show_idx:
730 return 'r%s:%s' % (commit.idx, raw_id)
734 return 'r%s:%s' % (commit.idx, raw_id)
731 else:
735 else:
732 return '%s' % (raw_id, )
736 return '%s' % (raw_id, )
733
737
734
738
735 def format_date(date):
739 def format_date(date):
736 """
740 """
737 use a standardized formatting for dates used in RhodeCode
741 use a standardized formatting for dates used in RhodeCode
738
742
739 :param date: date/datetime object
743 :param date: date/datetime object
740 :return: formatted date
744 :return: formatted date
741 """
745 """
742
746
743 if date:
747 if date:
744 _fmt = "%a, %d %b %Y %H:%M:%S"
748 _fmt = "%a, %d %b %Y %H:%M:%S"
745 return safe_unicode(date.strftime(_fmt))
749 return safe_unicode(date.strftime(_fmt))
746
750
747 return u""
751 return u""
748
752
749
753
750 class _RepoChecker(object):
754 class _RepoChecker(object):
751
755
752 def __init__(self, backend_alias):
756 def __init__(self, backend_alias):
753 self._backend_alias = backend_alias
757 self._backend_alias = backend_alias
754
758
755 def __call__(self, repository):
759 def __call__(self, repository):
756 if hasattr(repository, 'alias'):
760 if hasattr(repository, 'alias'):
757 _type = repository.alias
761 _type = repository.alias
758 elif hasattr(repository, 'repo_type'):
762 elif hasattr(repository, 'repo_type'):
759 _type = repository.repo_type
763 _type = repository.repo_type
760 else:
764 else:
761 _type = repository
765 _type = repository
762 return _type == self._backend_alias
766 return _type == self._backend_alias
763
767
764
768
765 is_git = _RepoChecker('git')
769 is_git = _RepoChecker('git')
766 is_hg = _RepoChecker('hg')
770 is_hg = _RepoChecker('hg')
767 is_svn = _RepoChecker('svn')
771 is_svn = _RepoChecker('svn')
768
772
769
773
770 def get_repo_type_by_name(repo_name):
774 def get_repo_type_by_name(repo_name):
771 repo = Repository.get_by_repo_name(repo_name)
775 repo = Repository.get_by_repo_name(repo_name)
772 if repo:
776 if repo:
773 return repo.repo_type
777 return repo.repo_type
774
778
775
779
776 def is_svn_without_proxy(repository):
780 def is_svn_without_proxy(repository):
777 if is_svn(repository):
781 if is_svn(repository):
778 from rhodecode.model.settings import VcsSettingsModel
782 from rhodecode.model.settings import VcsSettingsModel
779 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
783 conf = VcsSettingsModel().get_ui_settings_as_config_obj()
780 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
784 return not str2bool(conf.get('vcs_svn_proxy', 'http_requests_enabled'))
781 return False
785 return False
782
786
783
787
784 def discover_user(author):
788 def discover_user(author):
785 """
789 """
786 Tries to discover RhodeCode User based on the autho string. Author string
790 Tries to discover RhodeCode User based on the autho string. Author string
787 is typically `FirstName LastName <email@address.com>`
791 is typically `FirstName LastName <email@address.com>`
788 """
792 """
789
793
790 # if author is already an instance use it for extraction
794 # if author is already an instance use it for extraction
791 if isinstance(author, User):
795 if isinstance(author, User):
792 return author
796 return author
793
797
794 # Valid email in the attribute passed, see if they're in the system
798 # Valid email in the attribute passed, see if they're in the system
795 _email = author_email(author)
799 _email = author_email(author)
796 if _email != '':
800 if _email != '':
797 user = User.get_by_email(_email, case_insensitive=True, cache=True)
801 user = User.get_by_email(_email, case_insensitive=True, cache=True)
798 if user is not None:
802 if user is not None:
799 return user
803 return user
800
804
801 # Maybe it's a username, we try to extract it and fetch by username ?
805 # Maybe it's a username, we try to extract it and fetch by username ?
802 _author = author_name(author)
806 _author = author_name(author)
803 user = User.get_by_username(_author, case_insensitive=True, cache=True)
807 user = User.get_by_username(_author, case_insensitive=True, cache=True)
804 if user is not None:
808 if user is not None:
805 return user
809 return user
806
810
807 return None
811 return None
808
812
809
813
810 def email_or_none(author):
814 def email_or_none(author):
811 # extract email from the commit string
815 # extract email from the commit string
812 _email = author_email(author)
816 _email = author_email(author)
813
817
814 # If we have an email, use it, otherwise
818 # If we have an email, use it, otherwise
815 # see if it contains a username we can get an email from
819 # see if it contains a username we can get an email from
816 if _email != '':
820 if _email != '':
817 return _email
821 return _email
818 else:
822 else:
819 user = User.get_by_username(
823 user = User.get_by_username(
820 author_name(author), case_insensitive=True, cache=True)
824 author_name(author), case_insensitive=True, cache=True)
821
825
822 if user is not None:
826 if user is not None:
823 return user.email
827 return user.email
824
828
825 # No valid email, not a valid user in the system, none!
829 # No valid email, not a valid user in the system, none!
826 return None
830 return None
827
831
828
832
829 def link_to_user(author, length=0, **kwargs):
833 def link_to_user(author, length=0, **kwargs):
830 user = discover_user(author)
834 user = discover_user(author)
831 # user can be None, but if we have it already it means we can re-use it
835 # user can be None, but if we have it already it means we can re-use it
832 # in the person() function, so we save 1 intensive-query
836 # in the person() function, so we save 1 intensive-query
833 if user:
837 if user:
834 author = user
838 author = user
835
839
836 display_person = person(author, 'username_or_name_or_email')
840 display_person = person(author, 'username_or_name_or_email')
837 if length:
841 if length:
838 display_person = shorter(display_person, length)
842 display_person = shorter(display_person, length)
839
843
840 if user:
844 if user:
841 return link_to(
845 return link_to(
842 escape(display_person),
846 escape(display_person),
843 route_path('user_profile', username=user.username),
847 route_path('user_profile', username=user.username),
844 **kwargs)
848 **kwargs)
845 else:
849 else:
846 return escape(display_person)
850 return escape(display_person)
847
851
848
852
849 def link_to_group(users_group_name, **kwargs):
853 def link_to_group(users_group_name, **kwargs):
850 return link_to(
854 return link_to(
851 escape(users_group_name),
855 escape(users_group_name),
852 route_path('user_group_profile', user_group_name=users_group_name),
856 route_path('user_group_profile', user_group_name=users_group_name),
853 **kwargs)
857 **kwargs)
854
858
855
859
856 def person(author, show_attr="username_and_name"):
860 def person(author, show_attr="username_and_name"):
857 user = discover_user(author)
861 user = discover_user(author)
858 if user:
862 if user:
859 return getattr(user, show_attr)
863 return getattr(user, show_attr)
860 else:
864 else:
861 _author = author_name(author)
865 _author = author_name(author)
862 _email = email(author)
866 _email = email(author)
863 return _author or _email
867 return _author or _email
864
868
865
869
866 def author_string(email):
870 def author_string(email):
867 if email:
871 if email:
868 user = User.get_by_email(email, case_insensitive=True, cache=True)
872 user = User.get_by_email(email, case_insensitive=True, cache=True)
869 if user:
873 if user:
870 if user.first_name or user.last_name:
874 if user.first_name or user.last_name:
871 return '%s %s &lt;%s&gt;' % (
875 return '%s %s &lt;%s&gt;' % (
872 user.first_name, user.last_name, email)
876 user.first_name, user.last_name, email)
873 else:
877 else:
874 return email
878 return email
875 else:
879 else:
876 return email
880 return email
877 else:
881 else:
878 return None
882 return None
879
883
880
884
881 def person_by_id(id_, show_attr="username_and_name"):
885 def person_by_id(id_, show_attr="username_and_name"):
882 # attr to return from fetched user
886 # attr to return from fetched user
883 person_getter = lambda usr: getattr(usr, show_attr)
887 person_getter = lambda usr: getattr(usr, show_attr)
884
888
885 #maybe it's an ID ?
889 #maybe it's an ID ?
886 if str(id_).isdigit() or isinstance(id_, int):
890 if str(id_).isdigit() or isinstance(id_, int):
887 id_ = int(id_)
891 id_ = int(id_)
888 user = User.get(id_)
892 user = User.get(id_)
889 if user is not None:
893 if user is not None:
890 return person_getter(user)
894 return person_getter(user)
891 return id_
895 return id_
892
896
893
897
894 def gravatar_with_user(request, author, show_disabled=False):
898 def gravatar_with_user(request, author, show_disabled=False):
895 _render = request.get_partial_renderer(
899 _render = request.get_partial_renderer(
896 'rhodecode:templates/base/base.mako')
900 'rhodecode:templates/base/base.mako')
897 return _render('gravatar_with_user', author, show_disabled=show_disabled)
901 return _render('gravatar_with_user', author, show_disabled=show_disabled)
898
902
899
903
900 tags_paterns = OrderedDict((
904 tags_paterns = OrderedDict((
901 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
905 ('lang', (re.compile(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+\.]*)\]'),
902 '<div class="metatag" tag="lang">\\2</div>')),
906 '<div class="metatag" tag="lang">\\2</div>')),
903
907
904 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
908 ('see', (re.compile(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
905 '<div class="metatag" tag="see">see: \\1 </div>')),
909 '<div class="metatag" tag="see">see: \\1 </div>')),
906
910
907 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
911 ('url', (re.compile(r'\[url\ \=\&gt;\ \[([a-zA-Z0-9\ \.\-\_]+)\]\((http://|https://|/)(.*?)\)\]'),
908 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
912 '<div class="metatag" tag="url"> <a href="\\2\\3">\\1</a> </div>')),
909
913
910 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
914 ('license', (re.compile(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]'),
911 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
915 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>')),
912
916
913 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
917 ('ref', (re.compile(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]'),
914 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
918 '<div class="metatag" tag="ref \\1">\\1: <a href="/\\2">\\2</a></div>')),
915
919
916 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
920 ('state', (re.compile(r'\[(stable|featured|stale|dead|dev|deprecated)\]'),
917 '<div class="metatag" tag="state \\1">\\1</div>')),
921 '<div class="metatag" tag="state \\1">\\1</div>')),
918
922
919 # label in grey
923 # label in grey
920 ('label', (re.compile(r'\[([a-z]+)\]'),
924 ('label', (re.compile(r'\[([a-z]+)\]'),
921 '<div class="metatag" tag="label">\\1</div>')),
925 '<div class="metatag" tag="label">\\1</div>')),
922
926
923 # generic catch all in grey
927 # generic catch all in grey
924 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
928 ('generic', (re.compile(r'\[([a-zA-Z0-9\.\-\_]+)\]'),
925 '<div class="metatag" tag="generic">\\1</div>')),
929 '<div class="metatag" tag="generic">\\1</div>')),
926 ))
930 ))
927
931
928
932
929 def extract_metatags(value):
933 def extract_metatags(value):
930 """
934 """
931 Extract supported meta-tags from given text value
935 Extract supported meta-tags from given text value
932 """
936 """
933 tags = []
937 tags = []
934 if not value:
938 if not value:
935 return tags, ''
939 return tags, ''
936
940
937 for key, val in tags_paterns.items():
941 for key, val in tags_paterns.items():
938 pat, replace_html = val
942 pat, replace_html = val
939 tags.extend([(key, x.group()) for x in pat.finditer(value)])
943 tags.extend([(key, x.group()) for x in pat.finditer(value)])
940 value = pat.sub('', value)
944 value = pat.sub('', value)
941
945
942 return tags, value
946 return tags, value
943
947
944
948
945 def style_metatag(tag_type, value):
949 def style_metatag(tag_type, value):
946 """
950 """
947 converts tags from value into html equivalent
951 converts tags from value into html equivalent
948 """
952 """
949 if not value:
953 if not value:
950 return ''
954 return ''
951
955
952 html_value = value
956 html_value = value
953 tag_data = tags_paterns.get(tag_type)
957 tag_data = tags_paterns.get(tag_type)
954 if tag_data:
958 if tag_data:
955 pat, replace_html = tag_data
959 pat, replace_html = tag_data
956 # convert to plain `unicode` instead of a markup tag to be used in
960 # convert to plain `unicode` instead of a markup tag to be used in
957 # regex expressions. safe_unicode doesn't work here
961 # regex expressions. safe_unicode doesn't work here
958 html_value = pat.sub(replace_html, unicode(value))
962 html_value = pat.sub(replace_html, unicode(value))
959
963
960 return html_value
964 return html_value
961
965
962
966
963 def bool2icon(value, show_at_false=True):
967 def bool2icon(value, show_at_false=True):
964 """
968 """
965 Returns boolean value of a given value, represented as html element with
969 Returns boolean value of a given value, represented as html element with
966 classes that will represent icons
970 classes that will represent icons
967
971
968 :param value: given value to convert to html node
972 :param value: given value to convert to html node
969 """
973 """
970
974
971 if value: # does bool conversion
975 if value: # does bool conversion
972 return HTML.tag('i', class_="icon-true")
976 return HTML.tag('i', class_="icon-true")
973 else: # not true as bool
977 else: # not true as bool
974 if show_at_false:
978 if show_at_false:
975 return HTML.tag('i', class_="icon-false")
979 return HTML.tag('i', class_="icon-false")
976 return HTML.tag('i')
980 return HTML.tag('i')
977
981
978 #==============================================================================
982 #==============================================================================
979 # PERMS
983 # PERMS
980 #==============================================================================
984 #==============================================================================
981 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
985 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
982 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
986 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
983 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
987 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token, \
984 csrf_token_key
988 csrf_token_key
985
989
986
990
987 #==============================================================================
991 #==============================================================================
988 # GRAVATAR URL
992 # GRAVATAR URL
989 #==============================================================================
993 #==============================================================================
990 class InitialsGravatar(object):
994 class InitialsGravatar(object):
991 def __init__(self, email_address, first_name, last_name, size=30,
995 def __init__(self, email_address, first_name, last_name, size=30,
992 background=None, text_color='#fff'):
996 background=None, text_color='#fff'):
993 self.size = size
997 self.size = size
994 self.first_name = first_name
998 self.first_name = first_name
995 self.last_name = last_name
999 self.last_name = last_name
996 self.email_address = email_address
1000 self.email_address = email_address
997 self.background = background or self.str2color(email_address)
1001 self.background = background or self.str2color(email_address)
998 self.text_color = text_color
1002 self.text_color = text_color
999
1003
1000 def get_color_bank(self):
1004 def get_color_bank(self):
1001 """
1005 """
1002 returns a predefined list of colors that gravatars can use.
1006 returns a predefined list of colors that gravatars can use.
1003 Those are randomized distinct colors that guarantee readability and
1007 Those are randomized distinct colors that guarantee readability and
1004 uniqueness.
1008 uniqueness.
1005
1009
1006 generated with: http://phrogz.net/css/distinct-colors.html
1010 generated with: http://phrogz.net/css/distinct-colors.html
1007 """
1011 """
1008 return [
1012 return [
1009 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1013 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
1010 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1014 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
1011 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1015 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
1012 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1016 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
1013 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1017 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
1014 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1018 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
1015 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1019 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
1016 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1020 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
1017 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1021 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
1018 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1022 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
1019 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1023 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
1020 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1024 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
1021 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1025 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
1022 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1026 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
1023 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1027 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
1024 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1028 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
1025 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1029 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
1026 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1030 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
1027 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1031 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
1028 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1032 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
1029 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1033 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
1030 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1034 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
1031 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1035 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
1032 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1036 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
1033 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1037 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
1034 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1038 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
1035 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1039 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
1036 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1040 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
1037 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1041 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
1038 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1042 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
1039 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1043 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
1040 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1044 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
1041 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1045 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
1042 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1046 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
1043 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1047 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
1044 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1048 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
1045 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1049 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
1046 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1050 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
1047 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1051 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
1048 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1052 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
1049 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1053 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
1050 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1054 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
1051 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1055 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
1052 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1056 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
1053 '#4f8c46', '#368dd9', '#5c0073'
1057 '#4f8c46', '#368dd9', '#5c0073'
1054 ]
1058 ]
1055
1059
1056 def rgb_to_hex_color(self, rgb_tuple):
1060 def rgb_to_hex_color(self, rgb_tuple):
1057 """
1061 """
1058 Converts an rgb_tuple passed to an hex color.
1062 Converts an rgb_tuple passed to an hex color.
1059
1063
1060 :param rgb_tuple: tuple with 3 ints represents rgb color space
1064 :param rgb_tuple: tuple with 3 ints represents rgb color space
1061 """
1065 """
1062 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1066 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
1063
1067
1064 def email_to_int_list(self, email_str):
1068 def email_to_int_list(self, email_str):
1065 """
1069 """
1066 Get every byte of the hex digest value of email and turn it to integer.
1070 Get every byte of the hex digest value of email and turn it to integer.
1067 It's going to be always between 0-255
1071 It's going to be always between 0-255
1068 """
1072 """
1069 digest = md5_safe(email_str.lower())
1073 digest = md5_safe(email_str.lower())
1070 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1074 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
1071
1075
1072 def pick_color_bank_index(self, email_str, color_bank):
1076 def pick_color_bank_index(self, email_str, color_bank):
1073 return self.email_to_int_list(email_str)[0] % len(color_bank)
1077 return self.email_to_int_list(email_str)[0] % len(color_bank)
1074
1078
1075 def str2color(self, email_str):
1079 def str2color(self, email_str):
1076 """
1080 """
1077 Tries to map in a stable algorithm an email to color
1081 Tries to map in a stable algorithm an email to color
1078
1082
1079 :param email_str:
1083 :param email_str:
1080 """
1084 """
1081 color_bank = self.get_color_bank()
1085 color_bank = self.get_color_bank()
1082 # pick position (module it's length so we always find it in the
1086 # pick position (module it's length so we always find it in the
1083 # bank even if it's smaller than 256 values
1087 # bank even if it's smaller than 256 values
1084 pos = self.pick_color_bank_index(email_str, color_bank)
1088 pos = self.pick_color_bank_index(email_str, color_bank)
1085 return color_bank[pos]
1089 return color_bank[pos]
1086
1090
1087 def normalize_email(self, email_address):
1091 def normalize_email(self, email_address):
1088 import unicodedata
1092 import unicodedata
1089 # default host used to fill in the fake/missing email
1093 # default host used to fill in the fake/missing email
1090 default_host = u'localhost'
1094 default_host = u'localhost'
1091
1095
1092 if not email_address:
1096 if not email_address:
1093 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1097 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1094
1098
1095 email_address = safe_unicode(email_address)
1099 email_address = safe_unicode(email_address)
1096
1100
1097 if u'@' not in email_address:
1101 if u'@' not in email_address:
1098 email_address = u'%s@%s' % (email_address, default_host)
1102 email_address = u'%s@%s' % (email_address, default_host)
1099
1103
1100 if email_address.endswith(u'@'):
1104 if email_address.endswith(u'@'):
1101 email_address = u'%s%s' % (email_address, default_host)
1105 email_address = u'%s%s' % (email_address, default_host)
1102
1106
1103 email_address = unicodedata.normalize('NFKD', email_address)\
1107 email_address = unicodedata.normalize('NFKD', email_address)\
1104 .encode('ascii', 'ignore')
1108 .encode('ascii', 'ignore')
1105 return email_address
1109 return email_address
1106
1110
1107 def get_initials(self):
1111 def get_initials(self):
1108 """
1112 """
1109 Returns 2 letter initials calculated based on the input.
1113 Returns 2 letter initials calculated based on the input.
1110 The algorithm picks first given email address, and takes first letter
1114 The algorithm picks first given email address, and takes first letter
1111 of part before @, and then the first letter of server name. In case
1115 of part before @, and then the first letter of server name. In case
1112 the part before @ is in a format of `somestring.somestring2` it replaces
1116 the part before @ is in a format of `somestring.somestring2` it replaces
1113 the server letter with first letter of somestring2
1117 the server letter with first letter of somestring2
1114
1118
1115 In case function was initialized with both first and lastname, this
1119 In case function was initialized with both first and lastname, this
1116 overrides the extraction from email by first letter of the first and
1120 overrides the extraction from email by first letter of the first and
1117 last name. We add special logic to that functionality, In case Full name
1121 last name. We add special logic to that functionality, In case Full name
1118 is compound, like Guido Von Rossum, we use last part of the last name
1122 is compound, like Guido Von Rossum, we use last part of the last name
1119 (Von Rossum) picking `R`.
1123 (Von Rossum) picking `R`.
1120
1124
1121 Function also normalizes the non-ascii characters to they ascii
1125 Function also normalizes the non-ascii characters to they ascii
1122 representation, eg Δ„ => A
1126 representation, eg Δ„ => A
1123 """
1127 """
1124 import unicodedata
1128 import unicodedata
1125 # replace non-ascii to ascii
1129 # replace non-ascii to ascii
1126 first_name = unicodedata.normalize(
1130 first_name = unicodedata.normalize(
1127 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1131 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1128 last_name = unicodedata.normalize(
1132 last_name = unicodedata.normalize(
1129 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1133 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1130
1134
1131 # do NFKD encoding, and also make sure email has proper format
1135 # do NFKD encoding, and also make sure email has proper format
1132 email_address = self.normalize_email(self.email_address)
1136 email_address = self.normalize_email(self.email_address)
1133
1137
1134 # first push the email initials
1138 # first push the email initials
1135 prefix, server = email_address.split('@', 1)
1139 prefix, server = email_address.split('@', 1)
1136
1140
1137 # check if prefix is maybe a 'first_name.last_name' syntax
1141 # check if prefix is maybe a 'first_name.last_name' syntax
1138 _dot_split = prefix.rsplit('.', 1)
1142 _dot_split = prefix.rsplit('.', 1)
1139 if len(_dot_split) == 2 and _dot_split[1]:
1143 if len(_dot_split) == 2 and _dot_split[1]:
1140 initials = [_dot_split[0][0], _dot_split[1][0]]
1144 initials = [_dot_split[0][0], _dot_split[1][0]]
1141 else:
1145 else:
1142 initials = [prefix[0], server[0]]
1146 initials = [prefix[0], server[0]]
1143
1147
1144 # then try to replace either first_name or last_name
1148 # then try to replace either first_name or last_name
1145 fn_letter = (first_name or " ")[0].strip()
1149 fn_letter = (first_name or " ")[0].strip()
1146 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1150 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1147
1151
1148 if fn_letter:
1152 if fn_letter:
1149 initials[0] = fn_letter
1153 initials[0] = fn_letter
1150
1154
1151 if ln_letter:
1155 if ln_letter:
1152 initials[1] = ln_letter
1156 initials[1] = ln_letter
1153
1157
1154 return ''.join(initials).upper()
1158 return ''.join(initials).upper()
1155
1159
1156 def get_img_data_by_type(self, font_family, img_type):
1160 def get_img_data_by_type(self, font_family, img_type):
1157 default_user = """
1161 default_user = """
1158 <svg xmlns="http://www.w3.org/2000/svg"
1162 <svg xmlns="http://www.w3.org/2000/svg"
1159 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1163 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1160 viewBox="-15 -10 439.165 429.164"
1164 viewBox="-15 -10 439.165 429.164"
1161
1165
1162 xml:space="preserve"
1166 xml:space="preserve"
1163 style="background:{background};" >
1167 style="background:{background};" >
1164
1168
1165 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1169 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1166 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1170 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1167 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1171 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1168 168.596,153.916,216.671,
1172 168.596,153.916,216.671,
1169 204.583,216.671z" fill="{text_color}"/>
1173 204.583,216.671z" fill="{text_color}"/>
1170 <path d="M407.164,374.717L360.88,
1174 <path d="M407.164,374.717L360.88,
1171 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1175 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1172 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1176 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1173 15.366-44.203,23.488-69.076,23.488c-24.877,
1177 15.366-44.203,23.488-69.076,23.488c-24.877,
1174 0-48.762-8.122-69.078-23.488
1178 0-48.762-8.122-69.078-23.488
1175 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1179 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1176 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1180 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1177 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1181 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1178 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1182 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1179 19.402-10.527 C409.699,390.129,
1183 19.402-10.527 C409.699,390.129,
1180 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1184 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1181 </svg>""".format(
1185 </svg>""".format(
1182 size=self.size,
1186 size=self.size,
1183 background='#979797', # @grey4
1187 background='#979797', # @grey4
1184 text_color=self.text_color,
1188 text_color=self.text_color,
1185 font_family=font_family)
1189 font_family=font_family)
1186
1190
1187 return {
1191 return {
1188 "default_user": default_user
1192 "default_user": default_user
1189 }[img_type]
1193 }[img_type]
1190
1194
1191 def get_img_data(self, svg_type=None):
1195 def get_img_data(self, svg_type=None):
1192 """
1196 """
1193 generates the svg metadata for image
1197 generates the svg metadata for image
1194 """
1198 """
1195 fonts = [
1199 fonts = [
1196 '-apple-system',
1200 '-apple-system',
1197 'BlinkMacSystemFont',
1201 'BlinkMacSystemFont',
1198 'Segoe UI',
1202 'Segoe UI',
1199 'Roboto',
1203 'Roboto',
1200 'Oxygen-Sans',
1204 'Oxygen-Sans',
1201 'Ubuntu',
1205 'Ubuntu',
1202 'Cantarell',
1206 'Cantarell',
1203 'Helvetica Neue',
1207 'Helvetica Neue',
1204 'sans-serif'
1208 'sans-serif'
1205 ]
1209 ]
1206 font_family = ','.join(fonts)
1210 font_family = ','.join(fonts)
1207 if svg_type:
1211 if svg_type:
1208 return self.get_img_data_by_type(font_family, svg_type)
1212 return self.get_img_data_by_type(font_family, svg_type)
1209
1213
1210 initials = self.get_initials()
1214 initials = self.get_initials()
1211 img_data = """
1215 img_data = """
1212 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1216 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1213 width="{size}" height="{size}"
1217 width="{size}" height="{size}"
1214 style="width: 100%; height: 100%; background-color: {background}"
1218 style="width: 100%; height: 100%; background-color: {background}"
1215 viewBox="0 0 {size} {size}">
1219 viewBox="0 0 {size} {size}">
1216 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1220 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1217 pointer-events="auto" fill="{text_color}"
1221 pointer-events="auto" fill="{text_color}"
1218 font-family="{font_family}"
1222 font-family="{font_family}"
1219 style="font-weight: 400; font-size: {f_size}px;">{text}
1223 style="font-weight: 400; font-size: {f_size}px;">{text}
1220 </text>
1224 </text>
1221 </svg>""".format(
1225 </svg>""".format(
1222 size=self.size,
1226 size=self.size,
1223 f_size=self.size/2.05, # scale the text inside the box nicely
1227 f_size=self.size/2.05, # scale the text inside the box nicely
1224 background=self.background,
1228 background=self.background,
1225 text_color=self.text_color,
1229 text_color=self.text_color,
1226 text=initials.upper(),
1230 text=initials.upper(),
1227 font_family=font_family)
1231 font_family=font_family)
1228
1232
1229 return img_data
1233 return img_data
1230
1234
1231 def generate_svg(self, svg_type=None):
1235 def generate_svg(self, svg_type=None):
1232 img_data = self.get_img_data(svg_type)
1236 img_data = self.get_img_data(svg_type)
1233 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1237 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1234
1238
1235
1239
1236 def initials_gravatar(email_address, first_name, last_name, size=30):
1240 def initials_gravatar(email_address, first_name, last_name, size=30):
1237 svg_type = None
1241 svg_type = None
1238 if email_address == User.DEFAULT_USER_EMAIL:
1242 if email_address == User.DEFAULT_USER_EMAIL:
1239 svg_type = 'default_user'
1243 svg_type = 'default_user'
1240 klass = InitialsGravatar(email_address, first_name, last_name, size)
1244 klass = InitialsGravatar(email_address, first_name, last_name, size)
1241 return klass.generate_svg(svg_type=svg_type)
1245 return klass.generate_svg(svg_type=svg_type)
1242
1246
1243
1247
1244 def gravatar_url(email_address, size=30, request=None):
1248 def gravatar_url(email_address, size=30, request=None):
1245 request = get_current_request()
1249 request = get_current_request()
1246 _use_gravatar = request.call_context.visual.use_gravatar
1250 _use_gravatar = request.call_context.visual.use_gravatar
1247 _gravatar_url = request.call_context.visual.gravatar_url
1251 _gravatar_url = request.call_context.visual.gravatar_url
1248
1252
1249 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1253 _gravatar_url = _gravatar_url or User.DEFAULT_GRAVATAR_URL
1250
1254
1251 email_address = email_address or User.DEFAULT_USER_EMAIL
1255 email_address = email_address or User.DEFAULT_USER_EMAIL
1252 if isinstance(email_address, unicode):
1256 if isinstance(email_address, unicode):
1253 # hashlib crashes on unicode items
1257 # hashlib crashes on unicode items
1254 email_address = safe_str(email_address)
1258 email_address = safe_str(email_address)
1255
1259
1256 # empty email or default user
1260 # empty email or default user
1257 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1261 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1258 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1262 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1259
1263
1260 if _use_gravatar:
1264 if _use_gravatar:
1261 # TODO: Disuse pyramid thread locals. Think about another solution to
1265 # TODO: Disuse pyramid thread locals. Think about another solution to
1262 # get the host and schema here.
1266 # get the host and schema here.
1263 request = get_current_request()
1267 request = get_current_request()
1264 tmpl = safe_str(_gravatar_url)
1268 tmpl = safe_str(_gravatar_url)
1265 tmpl = tmpl.replace('{email}', email_address)\
1269 tmpl = tmpl.replace('{email}', email_address)\
1266 .replace('{md5email}', md5_safe(email_address.lower())) \
1270 .replace('{md5email}', md5_safe(email_address.lower())) \
1267 .replace('{netloc}', request.host)\
1271 .replace('{netloc}', request.host)\
1268 .replace('{scheme}', request.scheme)\
1272 .replace('{scheme}', request.scheme)\
1269 .replace('{size}', safe_str(size))
1273 .replace('{size}', safe_str(size))
1270 return tmpl
1274 return tmpl
1271 else:
1275 else:
1272 return initials_gravatar(email_address, '', '', size=size)
1276 return initials_gravatar(email_address, '', '', size=size)
1273
1277
1274
1278
1275 class Page(_Page):
1279 class Page(_Page):
1276 """
1280 """
1277 Custom pager to match rendering style with paginator
1281 Custom pager to match rendering style with paginator
1278 """
1282 """
1279
1283
1280 def _get_pos(self, cur_page, max_page, items):
1284 def _get_pos(self, cur_page, max_page, items):
1281 edge = (items / 2) + 1
1285 edge = (items / 2) + 1
1282 if (cur_page <= edge):
1286 if (cur_page <= edge):
1283 radius = max(items / 2, items - cur_page)
1287 radius = max(items / 2, items - cur_page)
1284 elif (max_page - cur_page) < edge:
1288 elif (max_page - cur_page) < edge:
1285 radius = (items - 1) - (max_page - cur_page)
1289 radius = (items - 1) - (max_page - cur_page)
1286 else:
1290 else:
1287 radius = items / 2
1291 radius = items / 2
1288
1292
1289 left = max(1, (cur_page - (radius)))
1293 left = max(1, (cur_page - (radius)))
1290 right = min(max_page, cur_page + (radius))
1294 right = min(max_page, cur_page + (radius))
1291 return left, cur_page, right
1295 return left, cur_page, right
1292
1296
1293 def _range(self, regexp_match):
1297 def _range(self, regexp_match):
1294 """
1298 """
1295 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1299 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1296
1300
1297 Arguments:
1301 Arguments:
1298
1302
1299 regexp_match
1303 regexp_match
1300 A "re" (regular expressions) match object containing the
1304 A "re" (regular expressions) match object containing the
1301 radius of linked pages around the current page in
1305 radius of linked pages around the current page in
1302 regexp_match.group(1) as a string
1306 regexp_match.group(1) as a string
1303
1307
1304 This function is supposed to be called as a callable in
1308 This function is supposed to be called as a callable in
1305 re.sub.
1309 re.sub.
1306
1310
1307 """
1311 """
1308 radius = int(regexp_match.group(1))
1312 radius = int(regexp_match.group(1))
1309
1313
1310 # Compute the first and last page number within the radius
1314 # Compute the first and last page number within the radius
1311 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1315 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1312 # -> leftmost_page = 5
1316 # -> leftmost_page = 5
1313 # -> rightmost_page = 9
1317 # -> rightmost_page = 9
1314 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1318 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1315 self.last_page,
1319 self.last_page,
1316 (radius * 2) + 1)
1320 (radius * 2) + 1)
1317 nav_items = []
1321 nav_items = []
1318
1322
1319 # Create a link to the first page (unless we are on the first page
1323 # Create a link to the first page (unless we are on the first page
1320 # or there would be no need to insert '..' spacers)
1324 # or there would be no need to insert '..' spacers)
1321 if self.page != self.first_page and self.first_page < leftmost_page:
1325 if self.page != self.first_page and self.first_page < leftmost_page:
1322 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1326 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1323
1327
1324 # Insert dots if there are pages between the first page
1328 # Insert dots if there are pages between the first page
1325 # and the currently displayed page range
1329 # and the currently displayed page range
1326 if leftmost_page - self.first_page > 1:
1330 if leftmost_page - self.first_page > 1:
1327 # Wrap in a SPAN tag if nolink_attr is set
1331 # Wrap in a SPAN tag if nolink_attr is set
1328 text = '..'
1332 text = '..'
1329 if self.dotdot_attr:
1333 if self.dotdot_attr:
1330 text = HTML.span(c=text, **self.dotdot_attr)
1334 text = HTML.span(c=text, **self.dotdot_attr)
1331 nav_items.append(text)
1335 nav_items.append(text)
1332
1336
1333 for thispage in xrange(leftmost_page, rightmost_page + 1):
1337 for thispage in xrange(leftmost_page, rightmost_page + 1):
1334 # Hilight the current page number and do not use a link
1338 # Hilight the current page number and do not use a link
1335 if thispage == self.page:
1339 if thispage == self.page:
1336 text = '%s' % (thispage,)
1340 text = '%s' % (thispage,)
1337 # Wrap in a SPAN tag if nolink_attr is set
1341 # Wrap in a SPAN tag if nolink_attr is set
1338 if self.curpage_attr:
1342 if self.curpage_attr:
1339 text = HTML.span(c=text, **self.curpage_attr)
1343 text = HTML.span(c=text, **self.curpage_attr)
1340 nav_items.append(text)
1344 nav_items.append(text)
1341 # Otherwise create just a link to that page
1345 # Otherwise create just a link to that page
1342 else:
1346 else:
1343 text = '%s' % (thispage,)
1347 text = '%s' % (thispage,)
1344 nav_items.append(self._pagerlink(thispage, text))
1348 nav_items.append(self._pagerlink(thispage, text))
1345
1349
1346 # Insert dots if there are pages between the displayed
1350 # Insert dots if there are pages between the displayed
1347 # page numbers and the end of the page range
1351 # page numbers and the end of the page range
1348 if self.last_page - rightmost_page > 1:
1352 if self.last_page - rightmost_page > 1:
1349 text = '..'
1353 text = '..'
1350 # Wrap in a SPAN tag if nolink_attr is set
1354 # Wrap in a SPAN tag if nolink_attr is set
1351 if self.dotdot_attr:
1355 if self.dotdot_attr:
1352 text = HTML.span(c=text, **self.dotdot_attr)
1356 text = HTML.span(c=text, **self.dotdot_attr)
1353 nav_items.append(text)
1357 nav_items.append(text)
1354
1358
1355 # Create a link to the very last page (unless we are on the last
1359 # Create a link to the very last page (unless we are on the last
1356 # page or there would be no need to insert '..' spacers)
1360 # page or there would be no need to insert '..' spacers)
1357 if self.page != self.last_page and rightmost_page < self.last_page:
1361 if self.page != self.last_page and rightmost_page < self.last_page:
1358 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1362 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1359
1363
1360 ## prerender links
1364 ## prerender links
1361 #_page_link = url.current()
1365 #_page_link = url.current()
1362 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1366 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1363 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1367 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1364 return self.separator.join(nav_items)
1368 return self.separator.join(nav_items)
1365
1369
1366 def pager(self, format='~2~', page_param='page', partial_param='partial',
1370 def pager(self, format='~2~', page_param='page', partial_param='partial',
1367 show_if_single_page=False, separator=' ', onclick=None,
1371 show_if_single_page=False, separator=' ', onclick=None,
1368 symbol_first='<<', symbol_last='>>',
1372 symbol_first='<<', symbol_last='>>',
1369 symbol_previous='<', symbol_next='>',
1373 symbol_previous='<', symbol_next='>',
1370 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1374 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1371 curpage_attr={'class': 'pager_curpage'},
1375 curpage_attr={'class': 'pager_curpage'},
1372 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1376 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1373
1377
1374 self.curpage_attr = curpage_attr
1378 self.curpage_attr = curpage_attr
1375 self.separator = separator
1379 self.separator = separator
1376 self.pager_kwargs = kwargs
1380 self.pager_kwargs = kwargs
1377 self.page_param = page_param
1381 self.page_param = page_param
1378 self.partial_param = partial_param
1382 self.partial_param = partial_param
1379 self.onclick = onclick
1383 self.onclick = onclick
1380 self.link_attr = link_attr
1384 self.link_attr = link_attr
1381 self.dotdot_attr = dotdot_attr
1385 self.dotdot_attr = dotdot_attr
1382
1386
1383 # Don't show navigator if there is no more than one page
1387 # Don't show navigator if there is no more than one page
1384 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1388 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1385 return ''
1389 return ''
1386
1390
1387 from string import Template
1391 from string import Template
1388 # Replace ~...~ in token format by range of pages
1392 # Replace ~...~ in token format by range of pages
1389 result = re.sub(r'~(\d+)~', self._range, format)
1393 result = re.sub(r'~(\d+)~', self._range, format)
1390
1394
1391 # Interpolate '%' variables
1395 # Interpolate '%' variables
1392 result = Template(result).safe_substitute({
1396 result = Template(result).safe_substitute({
1393 'first_page': self.first_page,
1397 'first_page': self.first_page,
1394 'last_page': self.last_page,
1398 'last_page': self.last_page,
1395 'page': self.page,
1399 'page': self.page,
1396 'page_count': self.page_count,
1400 'page_count': self.page_count,
1397 'items_per_page': self.items_per_page,
1401 'items_per_page': self.items_per_page,
1398 'first_item': self.first_item,
1402 'first_item': self.first_item,
1399 'last_item': self.last_item,
1403 'last_item': self.last_item,
1400 'item_count': self.item_count,
1404 'item_count': self.item_count,
1401 'link_first': self.page > self.first_page and \
1405 'link_first': self.page > self.first_page and \
1402 self._pagerlink(self.first_page, symbol_first) or '',
1406 self._pagerlink(self.first_page, symbol_first) or '',
1403 'link_last': self.page < self.last_page and \
1407 'link_last': self.page < self.last_page and \
1404 self._pagerlink(self.last_page, symbol_last) or '',
1408 self._pagerlink(self.last_page, symbol_last) or '',
1405 'link_previous': self.previous_page and \
1409 'link_previous': self.previous_page and \
1406 self._pagerlink(self.previous_page, symbol_previous) \
1410 self._pagerlink(self.previous_page, symbol_previous) \
1407 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1411 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1408 'link_next': self.next_page and \
1412 'link_next': self.next_page and \
1409 self._pagerlink(self.next_page, symbol_next) \
1413 self._pagerlink(self.next_page, symbol_next) \
1410 or HTML.span(symbol_next, class_="pg-next disabled")
1414 or HTML.span(symbol_next, class_="pg-next disabled")
1411 })
1415 })
1412
1416
1413 return literal(result)
1417 return literal(result)
1414
1418
1415
1419
1416 #==============================================================================
1420 #==============================================================================
1417 # REPO PAGER, PAGER FOR REPOSITORY
1421 # REPO PAGER, PAGER FOR REPOSITORY
1418 #==============================================================================
1422 #==============================================================================
1419 class RepoPage(Page):
1423 class RepoPage(Page):
1420
1424
1421 def __init__(self, collection, page=1, items_per_page=20,
1425 def __init__(self, collection, page=1, items_per_page=20,
1422 item_count=None, url=None, **kwargs):
1426 item_count=None, url=None, **kwargs):
1423
1427
1424 """Create a "RepoPage" instance. special pager for paging
1428 """Create a "RepoPage" instance. special pager for paging
1425 repository
1429 repository
1426 """
1430 """
1427 self._url_generator = url
1431 self._url_generator = url
1428
1432
1429 # Safe the kwargs class-wide so they can be used in the pager() method
1433 # Safe the kwargs class-wide so they can be used in the pager() method
1430 self.kwargs = kwargs
1434 self.kwargs = kwargs
1431
1435
1432 # Save a reference to the collection
1436 # Save a reference to the collection
1433 self.original_collection = collection
1437 self.original_collection = collection
1434
1438
1435 self.collection = collection
1439 self.collection = collection
1436
1440
1437 # The self.page is the number of the current page.
1441 # The self.page is the number of the current page.
1438 # The first page has the number 1!
1442 # The first page has the number 1!
1439 try:
1443 try:
1440 self.page = int(page) # make it int() if we get it as a string
1444 self.page = int(page) # make it int() if we get it as a string
1441 except (ValueError, TypeError):
1445 except (ValueError, TypeError):
1442 self.page = 1
1446 self.page = 1
1443
1447
1444 self.items_per_page = items_per_page
1448 self.items_per_page = items_per_page
1445
1449
1446 # Unless the user tells us how many items the collections has
1450 # Unless the user tells us how many items the collections has
1447 # we calculate that ourselves.
1451 # we calculate that ourselves.
1448 if item_count is not None:
1452 if item_count is not None:
1449 self.item_count = item_count
1453 self.item_count = item_count
1450 else:
1454 else:
1451 self.item_count = len(self.collection)
1455 self.item_count = len(self.collection)
1452
1456
1453 # Compute the number of the first and last available page
1457 # Compute the number of the first and last available page
1454 if self.item_count > 0:
1458 if self.item_count > 0:
1455 self.first_page = 1
1459 self.first_page = 1
1456 self.page_count = int(math.ceil(float(self.item_count) /
1460 self.page_count = int(math.ceil(float(self.item_count) /
1457 self.items_per_page))
1461 self.items_per_page))
1458 self.last_page = self.first_page + self.page_count - 1
1462 self.last_page = self.first_page + self.page_count - 1
1459
1463
1460 # Make sure that the requested page number is the range of
1464 # Make sure that the requested page number is the range of
1461 # valid pages
1465 # valid pages
1462 if self.page > self.last_page:
1466 if self.page > self.last_page:
1463 self.page = self.last_page
1467 self.page = self.last_page
1464 elif self.page < self.first_page:
1468 elif self.page < self.first_page:
1465 self.page = self.first_page
1469 self.page = self.first_page
1466
1470
1467 # Note: the number of items on this page can be less than
1471 # Note: the number of items on this page can be less than
1468 # items_per_page if the last page is not full
1472 # items_per_page if the last page is not full
1469 self.first_item = max(0, (self.item_count) - (self.page *
1473 self.first_item = max(0, (self.item_count) - (self.page *
1470 items_per_page))
1474 items_per_page))
1471 self.last_item = ((self.item_count - 1) - items_per_page *
1475 self.last_item = ((self.item_count - 1) - items_per_page *
1472 (self.page - 1))
1476 (self.page - 1))
1473
1477
1474 self.items = list(self.collection[self.first_item:self.last_item + 1])
1478 self.items = list(self.collection[self.first_item:self.last_item + 1])
1475
1479
1476 # Links to previous and next page
1480 # Links to previous and next page
1477 if self.page > self.first_page:
1481 if self.page > self.first_page:
1478 self.previous_page = self.page - 1
1482 self.previous_page = self.page - 1
1479 else:
1483 else:
1480 self.previous_page = None
1484 self.previous_page = None
1481
1485
1482 if self.page < self.last_page:
1486 if self.page < self.last_page:
1483 self.next_page = self.page + 1
1487 self.next_page = self.page + 1
1484 else:
1488 else:
1485 self.next_page = None
1489 self.next_page = None
1486
1490
1487 # No items available
1491 # No items available
1488 else:
1492 else:
1489 self.first_page = None
1493 self.first_page = None
1490 self.page_count = 0
1494 self.page_count = 0
1491 self.last_page = None
1495 self.last_page = None
1492 self.first_item = None
1496 self.first_item = None
1493 self.last_item = None
1497 self.last_item = None
1494 self.previous_page = None
1498 self.previous_page = None
1495 self.next_page = None
1499 self.next_page = None
1496 self.items = []
1500 self.items = []
1497
1501
1498 # This is a subclass of the 'list' type. Initialise the list now.
1502 # This is a subclass of the 'list' type. Initialise the list now.
1499 list.__init__(self, reversed(self.items))
1503 list.__init__(self, reversed(self.items))
1500
1504
1501
1505
1502 def breadcrumb_repo_link(repo):
1506 def breadcrumb_repo_link(repo):
1503 """
1507 """
1504 Makes a breadcrumbs path link to repo
1508 Makes a breadcrumbs path link to repo
1505
1509
1506 ex::
1510 ex::
1507 group >> subgroup >> repo
1511 group >> subgroup >> repo
1508
1512
1509 :param repo: a Repository instance
1513 :param repo: a Repository instance
1510 """
1514 """
1511
1515
1512 path = [
1516 path = [
1513 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1517 link_to(group.name, route_path('repo_group_home', repo_group_name=group.group_name),
1514 title='last change:{}'.format(format_date(group.last_commit_change)))
1518 title='last change:{}'.format(format_date(group.last_commit_change)))
1515 for group in repo.groups_with_parents
1519 for group in repo.groups_with_parents
1516 ] + [
1520 ] + [
1517 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1521 link_to(repo.just_name, route_path('repo_summary', repo_name=repo.repo_name),
1518 title='last change:{}'.format(format_date(repo.last_commit_change)))
1522 title='last change:{}'.format(format_date(repo.last_commit_change)))
1519 ]
1523 ]
1520
1524
1521 return literal(' &raquo; '.join(path))
1525 return literal(' &raquo; '.join(path))
1522
1526
1523
1527
1524 def breadcrumb_repo_group_link(repo_group):
1528 def breadcrumb_repo_group_link(repo_group):
1525 """
1529 """
1526 Makes a breadcrumbs path link to repo
1530 Makes a breadcrumbs path link to repo
1527
1531
1528 ex::
1532 ex::
1529 group >> subgroup
1533 group >> subgroup
1530
1534
1531 :param repo_group: a Repository Group instance
1535 :param repo_group: a Repository Group instance
1532 """
1536 """
1533
1537
1534 path = [
1538 path = [
1535 link_to(group.name,
1539 link_to(group.name,
1536 route_path('repo_group_home', repo_group_name=group.group_name),
1540 route_path('repo_group_home', repo_group_name=group.group_name),
1537 title='last change:{}'.format(format_date(group.last_commit_change)))
1541 title='last change:{}'.format(format_date(group.last_commit_change)))
1538 for group in repo_group.parents
1542 for group in repo_group.parents
1539 ] + [
1543 ] + [
1540 link_to(repo_group.name,
1544 link_to(repo_group.name,
1541 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1545 route_path('repo_group_home', repo_group_name=repo_group.group_name),
1542 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1546 title='last change:{}'.format(format_date(repo_group.last_commit_change)))
1543 ]
1547 ]
1544
1548
1545 return literal(' &raquo; '.join(path))
1549 return literal(' &raquo; '.join(path))
1546
1550
1547
1551
1548 def format_byte_size_binary(file_size):
1552 def format_byte_size_binary(file_size):
1549 """
1553 """
1550 Formats file/folder sizes to standard.
1554 Formats file/folder sizes to standard.
1551 """
1555 """
1552 if file_size is None:
1556 if file_size is None:
1553 file_size = 0
1557 file_size = 0
1554
1558
1555 formatted_size = format_byte_size(file_size, binary=True)
1559 formatted_size = format_byte_size(file_size, binary=True)
1556 return formatted_size
1560 return formatted_size
1557
1561
1558
1562
1559 def urlify_text(text_, safe=True):
1563 def urlify_text(text_, safe=True):
1560 """
1564 """
1561 Extrac urls from text and make html links out of them
1565 Extrac urls from text and make html links out of them
1562
1566
1563 :param text_:
1567 :param text_:
1564 """
1568 """
1565
1569
1566 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1570 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1567 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1571 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1568
1572
1569 def url_func(match_obj):
1573 def url_func(match_obj):
1570 url_full = match_obj.groups()[0]
1574 url_full = match_obj.groups()[0]
1571 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1575 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1572 _newtext = url_pat.sub(url_func, text_)
1576 _newtext = url_pat.sub(url_func, text_)
1573 if safe:
1577 if safe:
1574 return literal(_newtext)
1578 return literal(_newtext)
1575 return _newtext
1579 return _newtext
1576
1580
1577
1581
1578 def urlify_commits(text_, repository):
1582 def urlify_commits(text_, repository):
1579 """
1583 """
1580 Extract commit ids from text and make link from them
1584 Extract commit ids from text and make link from them
1581
1585
1582 :param text_:
1586 :param text_:
1583 :param repository: repo name to build the URL with
1587 :param repository: repo name to build the URL with
1584 """
1588 """
1585
1589
1586 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1590 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1587
1591
1588 def url_func(match_obj):
1592 def url_func(match_obj):
1589 commit_id = match_obj.groups()[1]
1593 commit_id = match_obj.groups()[1]
1590 pref = match_obj.groups()[0]
1594 pref = match_obj.groups()[0]
1591 suf = match_obj.groups()[2]
1595 suf = match_obj.groups()[2]
1592
1596
1593 tmpl = (
1597 tmpl = (
1594 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1598 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1595 '%(commit_id)s</a>%(suf)s'
1599 '%(commit_id)s</a>%(suf)s'
1596 )
1600 )
1597 return tmpl % {
1601 return tmpl % {
1598 'pref': pref,
1602 'pref': pref,
1599 'cls': 'revision-link',
1603 'cls': 'revision-link',
1600 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1604 'url': route_url('repo_commit', repo_name=repository, commit_id=commit_id),
1601 'commit_id': commit_id,
1605 'commit_id': commit_id,
1602 'suf': suf
1606 'suf': suf
1603 }
1607 }
1604
1608
1605 newtext = URL_PAT.sub(url_func, text_)
1609 newtext = URL_PAT.sub(url_func, text_)
1606
1610
1607 return newtext
1611 return newtext
1608
1612
1609
1613
1610 def _process_url_func(match_obj, repo_name, uid, entry,
1614 def _process_url_func(match_obj, repo_name, uid, entry,
1611 return_raw_data=False, link_format='html'):
1615 return_raw_data=False, link_format='html'):
1612 pref = ''
1616 pref = ''
1613 if match_obj.group().startswith(' '):
1617 if match_obj.group().startswith(' '):
1614 pref = ' '
1618 pref = ' '
1615
1619
1616 issue_id = ''.join(match_obj.groups())
1620 issue_id = ''.join(match_obj.groups())
1617
1621
1618 if link_format == 'html':
1622 if link_format == 'html':
1619 tmpl = (
1623 tmpl = (
1620 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1624 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1621 '%(issue-prefix)s%(id-repr)s'
1625 '%(issue-prefix)s%(id-repr)s'
1622 '</a>')
1626 '</a>')
1623 elif link_format == 'rst':
1627 elif link_format == 'rst':
1624 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1628 tmpl = '`%(issue-prefix)s%(id-repr)s <%(url)s>`_'
1625 elif link_format == 'markdown':
1629 elif link_format == 'markdown':
1626 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1630 tmpl = '[%(issue-prefix)s%(id-repr)s](%(url)s)'
1627 else:
1631 else:
1628 raise ValueError('Bad link_format:{}'.format(link_format))
1632 raise ValueError('Bad link_format:{}'.format(link_format))
1629
1633
1630 (repo_name_cleaned,
1634 (repo_name_cleaned,
1631 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1635 parent_group_name) = RepoGroupModel()._get_group_name_and_parent(repo_name)
1632
1636
1633 # variables replacement
1637 # variables replacement
1634 named_vars = {
1638 named_vars = {
1635 'id': issue_id,
1639 'id': issue_id,
1636 'repo': repo_name,
1640 'repo': repo_name,
1637 'repo_name': repo_name_cleaned,
1641 'repo_name': repo_name_cleaned,
1638 'group_name': parent_group_name
1642 'group_name': parent_group_name
1639 }
1643 }
1640 # named regex variables
1644 # named regex variables
1641 named_vars.update(match_obj.groupdict())
1645 named_vars.update(match_obj.groupdict())
1642 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1646 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1643
1647
1644 def quote_cleaner(input_str):
1648 def quote_cleaner(input_str):
1645 """Remove quotes as it's HTML"""
1649 """Remove quotes as it's HTML"""
1646 return input_str.replace('"', '')
1650 return input_str.replace('"', '')
1647
1651
1648 data = {
1652 data = {
1649 'pref': pref,
1653 'pref': pref,
1650 'cls': quote_cleaner('issue-tracker-link'),
1654 'cls': quote_cleaner('issue-tracker-link'),
1651 'url': quote_cleaner(_url),
1655 'url': quote_cleaner(_url),
1652 'id-repr': issue_id,
1656 'id-repr': issue_id,
1653 'issue-prefix': entry['pref'],
1657 'issue-prefix': entry['pref'],
1654 'serv': entry['url'],
1658 'serv': entry['url'],
1655 }
1659 }
1656 if return_raw_data:
1660 if return_raw_data:
1657 return {
1661 return {
1658 'id': issue_id,
1662 'id': issue_id,
1659 'url': _url
1663 'url': _url
1660 }
1664 }
1661 return tmpl % data
1665 return tmpl % data
1662
1666
1663
1667
1664 def get_active_pattern_entries(repo_name):
1668 def get_active_pattern_entries(repo_name):
1665 repo = None
1669 repo = None
1666 if repo_name:
1670 if repo_name:
1667 # Retrieving repo_name to avoid invalid repo_name to explode on
1671 # Retrieving repo_name to avoid invalid repo_name to explode on
1668 # IssueTrackerSettingsModel but still passing invalid name further down
1672 # IssueTrackerSettingsModel but still passing invalid name further down
1669 repo = Repository.get_by_repo_name(repo_name, cache=True)
1673 repo = Repository.get_by_repo_name(repo_name, cache=True)
1670
1674
1671 settings_model = IssueTrackerSettingsModel(repo=repo)
1675 settings_model = IssueTrackerSettingsModel(repo=repo)
1672 active_entries = settings_model.get_settings(cache=True)
1676 active_entries = settings_model.get_settings(cache=True)
1673 return active_entries
1677 return active_entries
1674
1678
1675
1679
1676 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1680 def process_patterns(text_string, repo_name, link_format='html', active_entries=None):
1677
1681
1678 allowed_formats = ['html', 'rst', 'markdown']
1682 allowed_formats = ['html', 'rst', 'markdown']
1679 if link_format not in allowed_formats:
1683 if link_format not in allowed_formats:
1680 raise ValueError('Link format can be only one of:{} got {}'.format(
1684 raise ValueError('Link format can be only one of:{} got {}'.format(
1681 allowed_formats, link_format))
1685 allowed_formats, link_format))
1682
1686
1683 active_entries = active_entries or get_active_pattern_entries(repo_name)
1687 active_entries = active_entries or get_active_pattern_entries(repo_name)
1684 issues_data = []
1688 issues_data = []
1685 newtext = text_string
1689 newtext = text_string
1686
1690
1687 for uid, entry in active_entries.items():
1691 for uid, entry in active_entries.items():
1688 log.debug('found issue tracker entry with uid %s', uid)
1692 log.debug('found issue tracker entry with uid %s', uid)
1689
1693
1690 if not (entry['pat'] and entry['url']):
1694 if not (entry['pat'] and entry['url']):
1691 log.debug('skipping due to missing data')
1695 log.debug('skipping due to missing data')
1692 continue
1696 continue
1693
1697
1694 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1698 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s',
1695 uid, entry['pat'], entry['url'], entry['pref'])
1699 uid, entry['pat'], entry['url'], entry['pref'])
1696
1700
1697 try:
1701 try:
1698 pattern = re.compile(r'%s' % entry['pat'])
1702 pattern = re.compile(r'%s' % entry['pat'])
1699 except re.error:
1703 except re.error:
1700 log.exception(
1704 log.exception(
1701 'issue tracker pattern: `%s` failed to compile',
1705 'issue tracker pattern: `%s` failed to compile',
1702 entry['pat'])
1706 entry['pat'])
1703 continue
1707 continue
1704
1708
1705 data_func = partial(
1709 data_func = partial(
1706 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1710 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1707 return_raw_data=True)
1711 return_raw_data=True)
1708
1712
1709 for match_obj in pattern.finditer(text_string):
1713 for match_obj in pattern.finditer(text_string):
1710 issues_data.append(data_func(match_obj))
1714 issues_data.append(data_func(match_obj))
1711
1715
1712 url_func = partial(
1716 url_func = partial(
1713 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1717 _process_url_func, repo_name=repo_name, entry=entry, uid=uid,
1714 link_format=link_format)
1718 link_format=link_format)
1715
1719
1716 newtext = pattern.sub(url_func, newtext)
1720 newtext = pattern.sub(url_func, newtext)
1717 log.debug('processed prefix:uid `%s`', uid)
1721 log.debug('processed prefix:uid `%s`', uid)
1718
1722
1719 return newtext, issues_data
1723 return newtext, issues_data
1720
1724
1721
1725
1722 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1726 def urlify_commit_message(commit_text, repository=None, active_pattern_entries=None):
1723 """
1727 """
1724 Parses given text message and makes proper links.
1728 Parses given text message and makes proper links.
1725 issues are linked to given issue-server, and rest is a commit link
1729 issues are linked to given issue-server, and rest is a commit link
1726
1730
1727 :param commit_text:
1731 :param commit_text:
1728 :param repository:
1732 :param repository:
1729 """
1733 """
1730 def escaper(string):
1734 def escaper(string):
1731 return string.replace('<', '&lt;').replace('>', '&gt;')
1735 return string.replace('<', '&lt;').replace('>', '&gt;')
1732
1736
1733 newtext = escaper(commit_text)
1737 newtext = escaper(commit_text)
1734
1738
1735 # extract http/https links and make them real urls
1739 # extract http/https links and make them real urls
1736 newtext = urlify_text(newtext, safe=False)
1740 newtext = urlify_text(newtext, safe=False)
1737
1741
1738 # urlify commits - extract commit ids and make link out of them, if we have
1742 # urlify commits - extract commit ids and make link out of them, if we have
1739 # the scope of repository present.
1743 # the scope of repository present.
1740 if repository:
1744 if repository:
1741 newtext = urlify_commits(newtext, repository)
1745 newtext = urlify_commits(newtext, repository)
1742
1746
1743 # process issue tracker patterns
1747 # process issue tracker patterns
1744 newtext, issues = process_patterns(newtext, repository or '',
1748 newtext, issues = process_patterns(newtext, repository or '',
1745 active_entries=active_pattern_entries)
1749 active_entries=active_pattern_entries)
1746
1750
1747 return literal(newtext)
1751 return literal(newtext)
1748
1752
1749
1753
1750 def render_binary(repo_name, file_obj):
1754 def render_binary(repo_name, file_obj):
1751 """
1755 """
1752 Choose how to render a binary file
1756 Choose how to render a binary file
1753 """
1757 """
1754
1758
1755 filename = file_obj.name
1759 filename = file_obj.name
1756
1760
1757 # images
1761 # images
1758 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1762 for ext in ['*.png', '*.jpg', '*.ico', '*.gif']:
1759 if fnmatch.fnmatch(filename, pat=ext):
1763 if fnmatch.fnmatch(filename, pat=ext):
1760 alt = escape(filename)
1764 alt = escape(filename)
1761 src = route_path(
1765 src = route_path(
1762 'repo_file_raw', repo_name=repo_name,
1766 'repo_file_raw', repo_name=repo_name,
1763 commit_id=file_obj.commit.raw_id,
1767 commit_id=file_obj.commit.raw_id,
1764 f_path=file_obj.path)
1768 f_path=file_obj.path)
1765 return literal(
1769 return literal(
1766 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1770 '<img class="rendered-binary" alt="{}" src="{}">'.format(alt, src))
1767
1771
1768
1772
1769 def renderer_from_filename(filename, exclude=None):
1773 def renderer_from_filename(filename, exclude=None):
1770 """
1774 """
1771 choose a renderer based on filename, this works only for text based files
1775 choose a renderer based on filename, this works only for text based files
1772 """
1776 """
1773
1777
1774 # ipython
1778 # ipython
1775 for ext in ['*.ipynb']:
1779 for ext in ['*.ipynb']:
1776 if fnmatch.fnmatch(filename, pat=ext):
1780 if fnmatch.fnmatch(filename, pat=ext):
1777 return 'jupyter'
1781 return 'jupyter'
1778
1782
1779 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1783 is_markup = MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1780 if is_markup:
1784 if is_markup:
1781 return is_markup
1785 return is_markup
1782 return None
1786 return None
1783
1787
1784
1788
1785 def render(source, renderer='rst', mentions=False, relative_urls=None,
1789 def render(source, renderer='rst', mentions=False, relative_urls=None,
1786 repo_name=None):
1790 repo_name=None):
1787
1791
1788 def maybe_convert_relative_links(html_source):
1792 def maybe_convert_relative_links(html_source):
1789 if relative_urls:
1793 if relative_urls:
1790 return relative_links(html_source, relative_urls)
1794 return relative_links(html_source, relative_urls)
1791 return html_source
1795 return html_source
1792
1796
1793 if renderer == 'plain':
1797 if renderer == 'plain':
1794 return literal(
1798 return literal(
1795 MarkupRenderer.plain(source, leading_newline=False))
1799 MarkupRenderer.plain(source, leading_newline=False))
1796
1800
1797 elif renderer == 'rst':
1801 elif renderer == 'rst':
1798 if repo_name:
1802 if repo_name:
1799 # process patterns on comments if we pass in repo name
1803 # process patterns on comments if we pass in repo name
1800 source, issues = process_patterns(
1804 source, issues = process_patterns(
1801 source, repo_name, link_format='rst')
1805 source, repo_name, link_format='rst')
1802
1806
1803 return literal(
1807 return literal(
1804 '<div class="rst-block">%s</div>' %
1808 '<div class="rst-block">%s</div>' %
1805 maybe_convert_relative_links(
1809 maybe_convert_relative_links(
1806 MarkupRenderer.rst(source, mentions=mentions)))
1810 MarkupRenderer.rst(source, mentions=mentions)))
1807
1811
1808 elif renderer == 'markdown':
1812 elif renderer == 'markdown':
1809 if repo_name:
1813 if repo_name:
1810 # process patterns on comments if we pass in repo name
1814 # process patterns on comments if we pass in repo name
1811 source, issues = process_patterns(
1815 source, issues = process_patterns(
1812 source, repo_name, link_format='markdown')
1816 source, repo_name, link_format='markdown')
1813
1817
1814 return literal(
1818 return literal(
1815 '<div class="markdown-block">%s</div>' %
1819 '<div class="markdown-block">%s</div>' %
1816 maybe_convert_relative_links(
1820 maybe_convert_relative_links(
1817 MarkupRenderer.markdown(source, flavored=True,
1821 MarkupRenderer.markdown(source, flavored=True,
1818 mentions=mentions)))
1822 mentions=mentions)))
1819
1823
1820 elif renderer == 'jupyter':
1824 elif renderer == 'jupyter':
1821 return literal(
1825 return literal(
1822 '<div class="ipynb">%s</div>' %
1826 '<div class="ipynb">%s</div>' %
1823 maybe_convert_relative_links(
1827 maybe_convert_relative_links(
1824 MarkupRenderer.jupyter(source)))
1828 MarkupRenderer.jupyter(source)))
1825
1829
1826 # None means just show the file-source
1830 # None means just show the file-source
1827 return None
1831 return None
1828
1832
1829
1833
1830 def commit_status(repo, commit_id):
1834 def commit_status(repo, commit_id):
1831 return ChangesetStatusModel().get_status(repo, commit_id)
1835 return ChangesetStatusModel().get_status(repo, commit_id)
1832
1836
1833
1837
1834 def commit_status_lbl(commit_status):
1838 def commit_status_lbl(commit_status):
1835 return dict(ChangesetStatus.STATUSES).get(commit_status)
1839 return dict(ChangesetStatus.STATUSES).get(commit_status)
1836
1840
1837
1841
1838 def commit_time(repo_name, commit_id):
1842 def commit_time(repo_name, commit_id):
1839 repo = Repository.get_by_repo_name(repo_name)
1843 repo = Repository.get_by_repo_name(repo_name)
1840 commit = repo.get_commit(commit_id=commit_id)
1844 commit = repo.get_commit(commit_id=commit_id)
1841 return commit.date
1845 return commit.date
1842
1846
1843
1847
1844 def get_permission_name(key):
1848 def get_permission_name(key):
1845 return dict(Permission.PERMS).get(key)
1849 return dict(Permission.PERMS).get(key)
1846
1850
1847
1851
1848 def journal_filter_help(request):
1852 def journal_filter_help(request):
1849 _ = request.translate
1853 _ = request.translate
1850 from rhodecode.lib.audit_logger import ACTIONS
1854 from rhodecode.lib.audit_logger import ACTIONS
1851 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1855 actions = '\n'.join(textwrap.wrap(', '.join(sorted(ACTIONS.keys())), 80))
1852
1856
1853 return _(
1857 return _(
1854 'Example filter terms:\n' +
1858 'Example filter terms:\n' +
1855 ' repository:vcs\n' +
1859 ' repository:vcs\n' +
1856 ' username:marcin\n' +
1860 ' username:marcin\n' +
1857 ' username:(NOT marcin)\n' +
1861 ' username:(NOT marcin)\n' +
1858 ' action:*push*\n' +
1862 ' action:*push*\n' +
1859 ' ip:127.0.0.1\n' +
1863 ' ip:127.0.0.1\n' +
1860 ' date:20120101\n' +
1864 ' date:20120101\n' +
1861 ' date:[20120101100000 TO 20120102]\n' +
1865 ' date:[20120101100000 TO 20120102]\n' +
1862 '\n' +
1866 '\n' +
1863 'Actions: {actions}\n' +
1867 'Actions: {actions}\n' +
1864 '\n' +
1868 '\n' +
1865 'Generate wildcards using \'*\' character:\n' +
1869 'Generate wildcards using \'*\' character:\n' +
1866 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1870 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1867 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1871 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1868 '\n' +
1872 '\n' +
1869 'Optional AND / OR operators in queries\n' +
1873 'Optional AND / OR operators in queries\n' +
1870 ' "repository:vcs OR repository:test"\n' +
1874 ' "repository:vcs OR repository:test"\n' +
1871 ' "username:test AND repository:test*"\n'
1875 ' "username:test AND repository:test*"\n'
1872 ).format(actions=actions)
1876 ).format(actions=actions)
1873
1877
1874
1878
1875 def not_mapped_error(repo_name):
1879 def not_mapped_error(repo_name):
1876 from rhodecode.translation import _
1880 from rhodecode.translation import _
1877 flash(_('%s repository is not mapped to db perhaps'
1881 flash(_('%s repository is not mapped to db perhaps'
1878 ' it was created or renamed from the filesystem'
1882 ' it was created or renamed from the filesystem'
1879 ' please run the application again'
1883 ' please run the application again'
1880 ' in order to rescan repositories') % repo_name, category='error')
1884 ' in order to rescan repositories') % repo_name, category='error')
1881
1885
1882
1886
1883 def ip_range(ip_addr):
1887 def ip_range(ip_addr):
1884 from rhodecode.model.db import UserIpMap
1888 from rhodecode.model.db import UserIpMap
1885 s, e = UserIpMap._get_ip_range(ip_addr)
1889 s, e = UserIpMap._get_ip_range(ip_addr)
1886 return '%s - %s' % (s, e)
1890 return '%s - %s' % (s, e)
1887
1891
1888
1892
1889 def form(url, method='post', needs_csrf_token=True, **attrs):
1893 def form(url, method='post', needs_csrf_token=True, **attrs):
1890 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1894 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1891 if method.lower() != 'get' and needs_csrf_token:
1895 if method.lower() != 'get' and needs_csrf_token:
1892 raise Exception(
1896 raise Exception(
1893 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1897 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1894 'CSRF token. If the endpoint does not require such token you can ' +
1898 'CSRF token. If the endpoint does not require such token you can ' +
1895 'explicitly set the parameter needs_csrf_token to false.')
1899 'explicitly set the parameter needs_csrf_token to false.')
1896
1900
1897 return wh_form(url, method=method, **attrs)
1901 return wh_form(url, method=method, **attrs)
1898
1902
1899
1903
1900 def secure_form(form_url, method="POST", multipart=False, **attrs):
1904 def secure_form(form_url, method="POST", multipart=False, **attrs):
1901 """Start a form tag that points the action to an url. This
1905 """Start a form tag that points the action to an url. This
1902 form tag will also include the hidden field containing
1906 form tag will also include the hidden field containing
1903 the auth token.
1907 the auth token.
1904
1908
1905 The url options should be given either as a string, or as a
1909 The url options should be given either as a string, or as a
1906 ``url()`` function. The method for the form defaults to POST.
1910 ``url()`` function. The method for the form defaults to POST.
1907
1911
1908 Options:
1912 Options:
1909
1913
1910 ``multipart``
1914 ``multipart``
1911 If set to True, the enctype is set to "multipart/form-data".
1915 If set to True, the enctype is set to "multipart/form-data".
1912 ``method``
1916 ``method``
1913 The method to use when submitting the form, usually either
1917 The method to use when submitting the form, usually either
1914 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1918 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1915 hidden input with name _method is added to simulate the verb
1919 hidden input with name _method is added to simulate the verb
1916 over POST.
1920 over POST.
1917
1921
1918 """
1922 """
1919 from webhelpers.pylonslib.secure_form import insecure_form
1923 from webhelpers.pylonslib.secure_form import insecure_form
1920
1924
1921 if 'request' in attrs:
1925 if 'request' in attrs:
1922 session = attrs['request'].session
1926 session = attrs['request'].session
1923 del attrs['request']
1927 del attrs['request']
1924 else:
1928 else:
1925 raise ValueError(
1929 raise ValueError(
1926 'Calling this form requires request= to be passed as argument')
1930 'Calling this form requires request= to be passed as argument')
1927
1931
1928 form = insecure_form(form_url, method, multipart, **attrs)
1932 form = insecure_form(form_url, method, multipart, **attrs)
1929 token = literal(
1933 token = literal(
1930 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1934 '<input type="hidden" id="{}" name="{}" value="{}">'.format(
1931 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1935 csrf_token_key, csrf_token_key, get_csrf_token(session)))
1932
1936
1933 return literal("%s\n%s" % (form, token))
1937 return literal("%s\n%s" % (form, token))
1934
1938
1935
1939
1936 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1940 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1937 select_html = select(name, selected, options, **attrs)
1941 select_html = select(name, selected, options, **attrs)
1938 select2 = """
1942 select2 = """
1939 <script>
1943 <script>
1940 $(document).ready(function() {
1944 $(document).ready(function() {
1941 $('#%s').select2({
1945 $('#%s').select2({
1942 containerCssClass: 'drop-menu',
1946 containerCssClass: 'drop-menu',
1943 dropdownCssClass: 'drop-menu-dropdown',
1947 dropdownCssClass: 'drop-menu-dropdown',
1944 dropdownAutoWidth: true%s
1948 dropdownAutoWidth: true%s
1945 });
1949 });
1946 });
1950 });
1947 </script>
1951 </script>
1948 """
1952 """
1949 filter_option = """,
1953 filter_option = """,
1950 minimumResultsForSearch: -1
1954 minimumResultsForSearch: -1
1951 """
1955 """
1952 input_id = attrs.get('id') or name
1956 input_id = attrs.get('id') or name
1953 filter_enabled = "" if enable_filter else filter_option
1957 filter_enabled = "" if enable_filter else filter_option
1954 select_script = literal(select2 % (input_id, filter_enabled))
1958 select_script = literal(select2 % (input_id, filter_enabled))
1955
1959
1956 return literal(select_html+select_script)
1960 return literal(select_html+select_script)
1957
1961
1958
1962
1959 def get_visual_attr(tmpl_context_var, attr_name):
1963 def get_visual_attr(tmpl_context_var, attr_name):
1960 """
1964 """
1961 A safe way to get a variable from visual variable of template context
1965 A safe way to get a variable from visual variable of template context
1962
1966
1963 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1967 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1964 :param attr_name: name of the attribute we fetch from the c.visual
1968 :param attr_name: name of the attribute we fetch from the c.visual
1965 """
1969 """
1966 visual = getattr(tmpl_context_var, 'visual', None)
1970 visual = getattr(tmpl_context_var, 'visual', None)
1967 if not visual:
1971 if not visual:
1968 return
1972 return
1969 else:
1973 else:
1970 return getattr(visual, attr_name, None)
1974 return getattr(visual, attr_name, None)
1971
1975
1972
1976
1973 def get_last_path_part(file_node):
1977 def get_last_path_part(file_node):
1974 if not file_node.path:
1978 if not file_node.path:
1975 return u'/'
1979 return u'/'
1976
1980
1977 path = safe_unicode(file_node.path.split('/')[-1])
1981 path = safe_unicode(file_node.path.split('/')[-1])
1978 return u'../' + path
1982 return u'../' + path
1979
1983
1980
1984
1981 def route_url(*args, **kwargs):
1985 def route_url(*args, **kwargs):
1982 """
1986 """
1983 Wrapper around pyramids `route_url` (fully qualified url) function.
1987 Wrapper around pyramids `route_url` (fully qualified url) function.
1984 """
1988 """
1985 req = get_current_request()
1989 req = get_current_request()
1986 return req.route_url(*args, **kwargs)
1990 return req.route_url(*args, **kwargs)
1987
1991
1988
1992
1989 def route_path(*args, **kwargs):
1993 def route_path(*args, **kwargs):
1990 """
1994 """
1991 Wrapper around pyramids `route_path` function.
1995 Wrapper around pyramids `route_path` function.
1992 """
1996 """
1993 req = get_current_request()
1997 req = get_current_request()
1994 return req.route_path(*args, **kwargs)
1998 return req.route_path(*args, **kwargs)
1995
1999
1996
2000
1997 def route_path_or_none(*args, **kwargs):
2001 def route_path_or_none(*args, **kwargs):
1998 try:
2002 try:
1999 return route_path(*args, **kwargs)
2003 return route_path(*args, **kwargs)
2000 except KeyError:
2004 except KeyError:
2001 return None
2005 return None
2002
2006
2003
2007
2004 def current_route_path(request, **kw):
2008 def current_route_path(request, **kw):
2005 new_args = request.GET.mixed()
2009 new_args = request.GET.mixed()
2006 new_args.update(kw)
2010 new_args.update(kw)
2007 return request.current_route_path(_query=new_args)
2011 return request.current_route_path(_query=new_args)
2008
2012
2009
2013
2010 def api_call_example(method, args):
2014 def api_call_example(method, args):
2011 """
2015 """
2012 Generates an API call example via CURL
2016 Generates an API call example via CURL
2013 """
2017 """
2014 args_json = json.dumps(OrderedDict([
2018 args_json = json.dumps(OrderedDict([
2015 ('id', 1),
2019 ('id', 1),
2016 ('auth_token', 'SECRET'),
2020 ('auth_token', 'SECRET'),
2017 ('method', method),
2021 ('method', method),
2018 ('args', args)
2022 ('args', args)
2019 ]))
2023 ]))
2020 return literal(
2024 return literal(
2021 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
2025 "curl {api_url} -X POST -H 'content-type:text/plain' --data-binary '{data}'"
2022 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2026 "<br/><br/>SECRET can be found in <a href=\"{token_url}\">auth-tokens</a> page, "
2023 "and needs to be of `api calls` role."
2027 "and needs to be of `api calls` role."
2024 .format(
2028 .format(
2025 api_url=route_url('apiv2'),
2029 api_url=route_url('apiv2'),
2026 token_url=route_url('my_account_auth_tokens'),
2030 token_url=route_url('my_account_auth_tokens'),
2027 data=args_json))
2031 data=args_json))
2028
2032
2029
2033
2030 def notification_description(notification, request):
2034 def notification_description(notification, request):
2031 """
2035 """
2032 Generate notification human readable description based on notification type
2036 Generate notification human readable description based on notification type
2033 """
2037 """
2034 from rhodecode.model.notification import NotificationModel
2038 from rhodecode.model.notification import NotificationModel
2035 return NotificationModel().make_description(
2039 return NotificationModel().make_description(
2036 notification, translate=request.translate)
2040 notification, translate=request.translate)
2037
2041
2038
2042
2039 def go_import_header(request, db_repo=None):
2043 def go_import_header(request, db_repo=None):
2040 """
2044 """
2041 Creates a header for go-import functionality in Go Lang
2045 Creates a header for go-import functionality in Go Lang
2042 """
2046 """
2043
2047
2044 if not db_repo:
2048 if not db_repo:
2045 return
2049 return
2046 if 'go-get' not in request.GET:
2050 if 'go-get' not in request.GET:
2047 return
2051 return
2048
2052
2049 clone_url = db_repo.clone_url()
2053 clone_url = db_repo.clone_url()
2050 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2054 prefix = re.split(r'^https?:\/\/', clone_url)[-1]
2051 # we have a repo and go-get flag,
2055 # we have a repo and go-get flag,
2052 return literal('<meta name="go-import" content="{} {} {}">'.format(
2056 return literal('<meta name="go-import" content="{} {} {}">'.format(
2053 prefix, db_repo.repo_type, clone_url))
2057 prefix, db_repo.repo_type, clone_url))
2054
2058
2055
2059
2056 def reviewer_as_json(*args, **kwargs):
2060 def reviewer_as_json(*args, **kwargs):
2057 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2061 from rhodecode.apps.repository.utils import reviewer_as_json as _reviewer_as_json
2058 return _reviewer_as_json(*args, **kwargs)
2062 return _reviewer_as_json(*args, **kwargs)
2059
2063
2060
2064
2061 def get_repo_view_type(request):
2065 def get_repo_view_type(request):
2062 route_name = request.matched_route.name
2066 route_name = request.matched_route.name
2063 route_to_view_type = {
2067 route_to_view_type = {
2064 'repo_changelog': 'commits',
2068 'repo_changelog': 'commits',
2065 'repo_commits': 'commits',
2069 'repo_commits': 'commits',
2066 'repo_files': 'files',
2070 'repo_files': 'files',
2067 'repo_summary': 'summary',
2071 'repo_summary': 'summary',
2068 'repo_commit': 'commit'
2072 'repo_commit': 'commit'
2069 }
2073 }
2070
2074
2071 return route_to_view_type.get(route_name)
2075 return route_to_view_type.get(route_name)
@@ -1,2603 +1,2608 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 color: @grey4;
110 color: @grey4;
111 margin-left: 5px;
111 margin-left: 5px;
112
112
113 &:hover {
113 &:hover {
114 color: @grey2;
114 color: @grey2;
115 }
115 }
116 }
116 }
117
117
118 ul.simple-list{
118 ul.simple-list{
119 list-style: none;
119 list-style: none;
120 margin: 0;
120 margin: 0;
121 padding: 0;
121 padding: 0;
122 }
122 }
123
123
124 .main-content {
124 .main-content {
125 padding-bottom: @pagepadding;
125 padding-bottom: @pagepadding;
126 }
126 }
127
127
128 .wide-mode-wrapper {
128 .wide-mode-wrapper {
129 max-width:4000px !important;
129 max-width:4000px !important;
130 }
130 }
131
131
132 .wrapper {
132 .wrapper {
133 position: relative;
133 position: relative;
134 max-width: @wrapper-maxwidth;
134 max-width: @wrapper-maxwidth;
135 margin: 0 auto;
135 margin: 0 auto;
136 }
136 }
137
137
138 #content {
138 #content {
139 clear: both;
139 clear: both;
140 padding: 0 @contentpadding;
140 padding: 0 @contentpadding;
141 }
141 }
142
142
143 .advanced-settings-fields{
143 .advanced-settings-fields{
144 input{
144 input{
145 margin-left: @textmargin;
145 margin-left: @textmargin;
146 margin-right: @padding/2;
146 margin-right: @padding/2;
147 }
147 }
148 }
148 }
149
149
150 .cs_files_title {
150 .cs_files_title {
151 margin: @pagepadding 0 0;
151 margin: @pagepadding 0 0;
152 }
152 }
153
153
154 input.inline[type="file"] {
154 input.inline[type="file"] {
155 display: inline;
155 display: inline;
156 }
156 }
157
157
158 .error_page {
158 .error_page {
159 margin: 10% auto;
159 margin: 10% auto;
160
160
161 h1 {
161 h1 {
162 color: @grey2;
162 color: @grey2;
163 }
163 }
164
164
165 .alert {
165 .alert {
166 margin: @padding 0;
166 margin: @padding 0;
167 }
167 }
168
168
169 .error-branding {
169 .error-branding {
170 color: @grey4;
170 color: @grey4;
171 font-weight: @text-semibold-weight;
171 font-weight: @text-semibold-weight;
172 font-family: @text-semibold;
172 font-family: @text-semibold;
173 }
173 }
174
174
175 .error_message {
175 .error_message {
176 font-family: @text-regular;
176 font-family: @text-regular;
177 }
177 }
178
178
179 .sidebar {
179 .sidebar {
180 min-height: 275px;
180 min-height: 275px;
181 margin: 0;
181 margin: 0;
182 padding: 0 0 @sidebarpadding @sidebarpadding;
182 padding: 0 0 @sidebarpadding @sidebarpadding;
183 border: none;
183 border: none;
184 }
184 }
185
185
186 .main-content {
186 .main-content {
187 position: relative;
187 position: relative;
188 margin: 0 @sidebarpadding @sidebarpadding;
188 margin: 0 @sidebarpadding @sidebarpadding;
189 padding: 0 0 0 @sidebarpadding;
189 padding: 0 0 0 @sidebarpadding;
190 border-left: @border-thickness solid @grey5;
190 border-left: @border-thickness solid @grey5;
191
191
192 @media (max-width:767px) {
192 @media (max-width:767px) {
193 clear: both;
193 clear: both;
194 width: 100%;
194 width: 100%;
195 margin: 0;
195 margin: 0;
196 border: none;
196 border: none;
197 }
197 }
198 }
198 }
199
199
200 .inner-column {
200 .inner-column {
201 float: left;
201 float: left;
202 width: 29.75%;
202 width: 29.75%;
203 min-height: 150px;
203 min-height: 150px;
204 margin: @sidebarpadding 2% 0 0;
204 margin: @sidebarpadding 2% 0 0;
205 padding: 0 2% 0 0;
205 padding: 0 2% 0 0;
206 border-right: @border-thickness solid @grey5;
206 border-right: @border-thickness solid @grey5;
207
207
208 @media (max-width:767px) {
208 @media (max-width:767px) {
209 clear: both;
209 clear: both;
210 width: 100%;
210 width: 100%;
211 border: none;
211 border: none;
212 }
212 }
213
213
214 ul {
214 ul {
215 padding-left: 1.25em;
215 padding-left: 1.25em;
216 }
216 }
217
217
218 &:last-child {
218 &:last-child {
219 margin: @sidebarpadding 0 0;
219 margin: @sidebarpadding 0 0;
220 border: none;
220 border: none;
221 }
221 }
222
222
223 h4 {
223 h4 {
224 margin: 0 0 @padding;
224 margin: 0 0 @padding;
225 font-weight: @text-semibold-weight;
225 font-weight: @text-semibold-weight;
226 font-family: @text-semibold;
226 font-family: @text-semibold;
227 }
227 }
228 }
228 }
229 }
229 }
230 .error-page-logo {
230 .error-page-logo {
231 width: 130px;
231 width: 130px;
232 height: 160px;
232 height: 160px;
233 }
233 }
234
234
235 // HEADER
235 // HEADER
236 .header {
236 .header {
237
237
238 // TODO: johbo: Fix login pages, so that they work without a min-height
238 // TODO: johbo: Fix login pages, so that they work without a min-height
239 // for the header and then remove the min-height. I chose a smaller value
239 // for the header and then remove the min-height. I chose a smaller value
240 // intentionally here to avoid rendering issues in the main navigation.
240 // intentionally here to avoid rendering issues in the main navigation.
241 min-height: 49px;
241 min-height: 49px;
242
242
243 position: relative;
243 position: relative;
244 vertical-align: bottom;
244 vertical-align: bottom;
245 padding: 0 @header-padding;
245 padding: 0 @header-padding;
246 background-color: @grey1;
246 background-color: @grey1;
247 color: @grey5;
247 color: @grey5;
248
248
249 .title {
249 .title {
250 overflow: visible;
250 overflow: visible;
251 }
251 }
252
252
253 &:before,
253 &:before,
254 &:after {
254 &:after {
255 content: "";
255 content: "";
256 clear: both;
256 clear: both;
257 width: 100%;
257 width: 100%;
258 }
258 }
259
259
260 // TODO: johbo: Avoids breaking "Repositories" chooser
260 // TODO: johbo: Avoids breaking "Repositories" chooser
261 .select2-container .select2-choice .select2-arrow {
261 .select2-container .select2-choice .select2-arrow {
262 display: none;
262 display: none;
263 }
263 }
264 }
264 }
265
265
266 #header-inner {
266 #header-inner {
267 &.title {
267 &.title {
268 margin: 0;
268 margin: 0;
269 }
269 }
270 &:before,
270 &:before,
271 &:after {
271 &:after {
272 content: "";
272 content: "";
273 clear: both;
273 clear: both;
274 }
274 }
275 }
275 }
276
276
277 // Gists
277 // Gists
278 #files_data {
278 #files_data {
279 clear: both; //for firefox
279 clear: both; //for firefox
280 }
280 }
281 #gistid {
281 #gistid {
282 margin-right: @padding;
282 margin-right: @padding;
283 }
283 }
284
284
285 // Global Settings Editor
285 // Global Settings Editor
286 .textarea.editor {
286 .textarea.editor {
287 float: left;
287 float: left;
288 position: relative;
288 position: relative;
289 max-width: @texteditor-width;
289 max-width: @texteditor-width;
290
290
291 select {
291 select {
292 position: absolute;
292 position: absolute;
293 top:10px;
293 top:10px;
294 right:0;
294 right:0;
295 }
295 }
296
296
297 .CodeMirror {
297 .CodeMirror {
298 margin: 0;
298 margin: 0;
299 }
299 }
300
300
301 .help-block {
301 .help-block {
302 margin: 0 0 @padding;
302 margin: 0 0 @padding;
303 padding:.5em;
303 padding:.5em;
304 background-color: @grey6;
304 background-color: @grey6;
305 &.pre-formatting {
305 &.pre-formatting {
306 white-space: pre;
306 white-space: pre;
307 }
307 }
308 }
308 }
309 }
309 }
310
310
311 ul.auth_plugins {
311 ul.auth_plugins {
312 margin: @padding 0 @padding @legend-width;
312 margin: @padding 0 @padding @legend-width;
313 padding: 0;
313 padding: 0;
314
314
315 li {
315 li {
316 margin-bottom: @padding;
316 margin-bottom: @padding;
317 line-height: 1em;
317 line-height: 1em;
318 list-style-type: none;
318 list-style-type: none;
319
319
320 .auth_buttons .btn {
320 .auth_buttons .btn {
321 margin-right: @padding;
321 margin-right: @padding;
322 }
322 }
323
323
324 }
324 }
325 }
325 }
326
326
327
327
328 // My Account PR list
328 // My Account PR list
329
329
330 #show_closed {
330 #show_closed {
331 margin: 0 1em 0 0;
331 margin: 0 1em 0 0;
332 }
332 }
333
333
334 .pullrequestlist {
334 .pullrequestlist {
335 .closed {
335 .closed {
336 background-color: @grey6;
336 background-color: @grey6;
337 }
337 }
338 .td-status {
338 .td-status {
339 padding-left: .5em;
339 padding-left: .5em;
340 }
340 }
341 .log-container .truncate {
341 .log-container .truncate {
342 height: 2.75em;
342 height: 2.75em;
343 white-space: pre-line;
343 white-space: pre-line;
344 }
344 }
345 table.rctable .user {
345 table.rctable .user {
346 padding-left: 0;
346 padding-left: 0;
347 }
347 }
348 table.rctable {
348 table.rctable {
349 td.td-description,
349 td.td-description,
350 .rc-user {
350 .rc-user {
351 min-width: auto;
351 min-width: auto;
352 }
352 }
353 }
353 }
354 }
354 }
355
355
356 // Pull Requests
356 // Pull Requests
357
357
358 .pullrequests_section_head {
358 .pullrequests_section_head {
359 display: block;
359 display: block;
360 clear: both;
360 clear: both;
361 margin: @padding 0;
361 margin: @padding 0;
362 font-weight: @text-bold-weight;
362 font-weight: @text-bold-weight;
363 font-family: @text-bold;
363 font-family: @text-bold;
364 }
364 }
365
365
366 .pr-origininfo, .pr-targetinfo {
366 .pr-origininfo, .pr-targetinfo {
367 position: relative;
367 position: relative;
368
368
369 .tag {
369 .tag {
370 display: inline-block;
370 display: inline-block;
371 margin: 0 1em .5em 0;
371 margin: 0 1em .5em 0;
372 }
372 }
373
373
374 .clone-url {
374 .clone-url {
375 display: inline-block;
375 display: inline-block;
376 margin: 0 0 .5em 0;
376 margin: 0 0 .5em 0;
377 padding: 0;
377 padding: 0;
378 line-height: 1.2em;
378 line-height: 1.2em;
379 }
379 }
380 }
380 }
381
381
382 .pr-mergeinfo {
382 .pr-mergeinfo {
383 min-width: 95% !important;
383 min-width: 95% !important;
384 padding: 0 !important;
384 padding: 0 !important;
385 border: 0;
385 border: 0;
386 }
386 }
387 .pr-mergeinfo-copy {
387 .pr-mergeinfo-copy {
388 padding: 0 0;
388 padding: 0 0;
389 }
389 }
390
390
391 .pr-pullinfo {
391 .pr-pullinfo {
392 min-width: 95% !important;
392 min-width: 95% !important;
393 padding: 0 !important;
393 padding: 0 !important;
394 border: 0;
394 border: 0;
395 }
395 }
396 .pr-pullinfo-copy {
396 .pr-pullinfo-copy {
397 padding: 0 0;
397 padding: 0 0;
398 }
398 }
399
399
400
400
401 #pr-title-input {
401 #pr-title-input {
402 width: 72%;
402 width: 72%;
403 font-size: 1em;
403 font-size: 1em;
404 margin: 0;
404 margin: 0;
405 padding: 0 0 0 @padding/4;
405 padding: 0 0 0 @padding/4;
406 line-height: 1.7em;
406 line-height: 1.7em;
407 color: @text-color;
407 color: @text-color;
408 letter-spacing: .02em;
408 letter-spacing: .02em;
409 font-weight: @text-bold-weight;
409 font-weight: @text-bold-weight;
410 font-family: @text-bold;
410 font-family: @text-bold;
411 }
411 }
412
412
413 #pullrequest_title {
413 #pullrequest_title {
414 width: 100%;
414 width: 100%;
415 box-sizing: border-box;
415 box-sizing: border-box;
416 }
416 }
417
417
418 #pr_open_message {
418 #pr_open_message {
419 border: @border-thickness solid #fff;
419 border: @border-thickness solid #fff;
420 border-radius: @border-radius;
420 border-radius: @border-radius;
421 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
421 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
422 text-align: left;
422 text-align: left;
423 overflow: hidden;
423 overflow: hidden;
424 }
424 }
425
425
426 .pr-submit-button {
426 .pr-submit-button {
427 float: right;
427 float: right;
428 margin: 0 0 0 5px;
428 margin: 0 0 0 5px;
429 }
429 }
430
430
431 .pr-spacing-container {
431 .pr-spacing-container {
432 padding: 20px;
432 padding: 20px;
433 clear: both
433 clear: both
434 }
434 }
435
435
436 #pr-description-input {
436 #pr-description-input {
437 margin-bottom: 0;
437 margin-bottom: 0;
438 }
438 }
439
439
440 .pr-description-label {
440 .pr-description-label {
441 vertical-align: top;
441 vertical-align: top;
442 }
442 }
443
443
444 .perms_section_head {
444 .perms_section_head {
445 min-width: 625px;
445 min-width: 625px;
446
446
447 h2 {
447 h2 {
448 margin-bottom: 0;
448 margin-bottom: 0;
449 }
449 }
450
450
451 .label-checkbox {
451 .label-checkbox {
452 float: left;
452 float: left;
453 }
453 }
454
454
455 &.field {
455 &.field {
456 margin: @space 0 @padding;
456 margin: @space 0 @padding;
457 }
457 }
458
458
459 &:first-child.field {
459 &:first-child.field {
460 margin-top: 0;
460 margin-top: 0;
461
461
462 .label {
462 .label {
463 margin-top: 0;
463 margin-top: 0;
464 padding-top: 0;
464 padding-top: 0;
465 }
465 }
466
466
467 .radios {
467 .radios {
468 padding-top: 0;
468 padding-top: 0;
469 }
469 }
470 }
470 }
471
471
472 .radios {
472 .radios {
473 position: relative;
473 position: relative;
474 width: 505px;
474 width: 505px;
475 }
475 }
476 }
476 }
477
477
478 //--- MODULES ------------------//
478 //--- MODULES ------------------//
479
479
480
480
481 // Server Announcement
481 // Server Announcement
482 #server-announcement {
482 #server-announcement {
483 width: 95%;
483 width: 95%;
484 margin: @padding auto;
484 margin: @padding auto;
485 padding: @padding;
485 padding: @padding;
486 border-width: 2px;
486 border-width: 2px;
487 border-style: solid;
487 border-style: solid;
488 .border-radius(2px);
488 .border-radius(2px);
489 font-weight: @text-bold-weight;
489 font-weight: @text-bold-weight;
490 font-family: @text-bold;
490 font-family: @text-bold;
491
491
492 &.info { border-color: @alert4; background-color: @alert4-inner; }
492 &.info { border-color: @alert4; background-color: @alert4-inner; }
493 &.warning { border-color: @alert3; background-color: @alert3-inner; }
493 &.warning { border-color: @alert3; background-color: @alert3-inner; }
494 &.error { border-color: @alert2; background-color: @alert2-inner; }
494 &.error { border-color: @alert2; background-color: @alert2-inner; }
495 &.success { border-color: @alert1; background-color: @alert1-inner; }
495 &.success { border-color: @alert1; background-color: @alert1-inner; }
496 &.neutral { border-color: @grey3; background-color: @grey6; }
496 &.neutral { border-color: @grey3; background-color: @grey6; }
497 }
497 }
498
498
499 // Fixed Sidebar Column
499 // Fixed Sidebar Column
500 .sidebar-col-wrapper {
500 .sidebar-col-wrapper {
501 padding-left: @sidebar-all-width;
501 padding-left: @sidebar-all-width;
502
502
503 .sidebar {
503 .sidebar {
504 width: @sidebar-width;
504 width: @sidebar-width;
505 margin-left: -@sidebar-all-width;
505 margin-left: -@sidebar-all-width;
506 }
506 }
507 }
507 }
508
508
509 .sidebar-col-wrapper.scw-small {
509 .sidebar-col-wrapper.scw-small {
510 padding-left: @sidebar-small-all-width;
510 padding-left: @sidebar-small-all-width;
511
511
512 .sidebar {
512 .sidebar {
513 width: @sidebar-small-width;
513 width: @sidebar-small-width;
514 margin-left: -@sidebar-small-all-width;
514 margin-left: -@sidebar-small-all-width;
515 }
515 }
516 }
516 }
517
517
518
518
519 // FOOTER
519 // FOOTER
520 #footer {
520 #footer {
521 padding: 0;
521 padding: 0;
522 text-align: center;
522 text-align: center;
523 vertical-align: middle;
523 vertical-align: middle;
524 color: @grey2;
524 color: @grey2;
525 font-size: 11px;
525 font-size: 11px;
526
526
527 p {
527 p {
528 margin: 0;
528 margin: 0;
529 padding: 1em;
529 padding: 1em;
530 line-height: 1em;
530 line-height: 1em;
531 }
531 }
532
532
533 .server-instance { //server instance
533 .server-instance { //server instance
534 display: none;
534 display: none;
535 }
535 }
536
536
537 .title {
537 .title {
538 float: none;
538 float: none;
539 margin: 0 auto;
539 margin: 0 auto;
540 }
540 }
541 }
541 }
542
542
543 button.close {
543 button.close {
544 padding: 0;
544 padding: 0;
545 cursor: pointer;
545 cursor: pointer;
546 background: transparent;
546 background: transparent;
547 border: 0;
547 border: 0;
548 .box-shadow(none);
548 .box-shadow(none);
549 -webkit-appearance: none;
549 -webkit-appearance: none;
550 }
550 }
551
551
552 .close {
552 .close {
553 float: right;
553 float: right;
554 font-size: 21px;
554 font-size: 21px;
555 font-family: @text-bootstrap;
555 font-family: @text-bootstrap;
556 line-height: 1em;
556 line-height: 1em;
557 font-weight: bold;
557 font-weight: bold;
558 color: @grey2;
558 color: @grey2;
559
559
560 &:hover,
560 &:hover,
561 &:focus {
561 &:focus {
562 color: @grey1;
562 color: @grey1;
563 text-decoration: none;
563 text-decoration: none;
564 cursor: pointer;
564 cursor: pointer;
565 }
565 }
566 }
566 }
567
567
568 // GRID
568 // GRID
569 .sorting,
569 .sorting,
570 .sorting_desc,
570 .sorting_desc,
571 .sorting_asc {
571 .sorting_asc {
572 cursor: pointer;
572 cursor: pointer;
573 }
573 }
574 .sorting_desc:after {
574 .sorting_desc:after {
575 content: "\00A0\25B2";
575 content: "\00A0\25B2";
576 font-size: .75em;
576 font-size: .75em;
577 }
577 }
578 .sorting_asc:after {
578 .sorting_asc:after {
579 content: "\00A0\25BC";
579 content: "\00A0\25BC";
580 font-size: .68em;
580 font-size: .68em;
581 }
581 }
582
582
583
583
584 .user_auth_tokens {
584 .user_auth_tokens {
585
585
586 &.truncate {
586 &.truncate {
587 white-space: nowrap;
587 white-space: nowrap;
588 overflow: hidden;
588 overflow: hidden;
589 text-overflow: ellipsis;
589 text-overflow: ellipsis;
590 }
590 }
591
591
592 .fields .field .input {
592 .fields .field .input {
593 margin: 0;
593 margin: 0;
594 }
594 }
595
595
596 input#description {
596 input#description {
597 width: 100px;
597 width: 100px;
598 margin: 0;
598 margin: 0;
599 }
599 }
600
600
601 .drop-menu {
601 .drop-menu {
602 // TODO: johbo: Remove this, should work out of the box when
602 // TODO: johbo: Remove this, should work out of the box when
603 // having multiple inputs inline
603 // having multiple inputs inline
604 margin: 0 0 0 5px;
604 margin: 0 0 0 5px;
605 }
605 }
606 }
606 }
607 #user_list_table {
607 #user_list_table {
608 .closed {
608 .closed {
609 background-color: @grey6;
609 background-color: @grey6;
610 }
610 }
611 }
611 }
612
612
613
613
614 input, textarea {
614 input, textarea {
615 &.disabled {
615 &.disabled {
616 opacity: .5;
616 opacity: .5;
617 }
617 }
618
618
619 &:hover {
619 &:hover {
620 border-color: @grey3;
620 border-color: @grey3;
621 box-shadow: @button-shadow;
621 box-shadow: @button-shadow;
622 }
622 }
623
623
624 &:focus {
624 &:focus {
625 border-color: @rcblue;
625 border-color: @rcblue;
626 box-shadow: @button-shadow;
626 box-shadow: @button-shadow;
627 }
627 }
628 }
628 }
629
629
630 // remove extra padding in firefox
630 // remove extra padding in firefox
631 input::-moz-focus-inner { border:0; padding:0 }
631 input::-moz-focus-inner { border:0; padding:0 }
632
632
633 .adjacent input {
633 .adjacent input {
634 margin-bottom: @padding;
634 margin-bottom: @padding;
635 }
635 }
636
636
637 .permissions_boxes {
637 .permissions_boxes {
638 display: block;
638 display: block;
639 }
639 }
640
640
641 //FORMS
641 //FORMS
642
642
643 .medium-inline,
643 .medium-inline,
644 input#description.medium-inline {
644 input#description.medium-inline {
645 display: inline;
645 display: inline;
646 width: @medium-inline-input-width;
646 width: @medium-inline-input-width;
647 min-width: 100px;
647 min-width: 100px;
648 }
648 }
649
649
650 select {
650 select {
651 //reset
651 //reset
652 -webkit-appearance: none;
652 -webkit-appearance: none;
653 -moz-appearance: none;
653 -moz-appearance: none;
654
654
655 display: inline-block;
655 display: inline-block;
656 height: 28px;
656 height: 28px;
657 width: auto;
657 width: auto;
658 margin: 0 @padding @padding 0;
658 margin: 0 @padding @padding 0;
659 padding: 0 18px 0 8px;
659 padding: 0 18px 0 8px;
660 line-height:1em;
660 line-height:1em;
661 font-size: @basefontsize;
661 font-size: @basefontsize;
662 border: @border-thickness solid @grey5;
662 border: @border-thickness solid @grey5;
663 border-radius: @border-radius;
663 border-radius: @border-radius;
664 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
664 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
665 color: @grey4;
665 color: @grey4;
666 box-shadow: @button-shadow;
666 box-shadow: @button-shadow;
667
667
668 &:after {
668 &:after {
669 content: "\00A0\25BE";
669 content: "\00A0\25BE";
670 }
670 }
671
671
672 &:focus, &:hover {
672 &:focus, &:hover {
673 outline: none;
673 outline: none;
674 border-color: @grey4;
674 border-color: @grey4;
675 color: @rcdarkblue;
675 color: @rcdarkblue;
676 }
676 }
677 }
677 }
678
678
679 option {
679 option {
680 &:focus {
680 &:focus {
681 outline: none;
681 outline: none;
682 }
682 }
683 }
683 }
684
684
685 input,
685 input,
686 textarea {
686 textarea {
687 padding: @input-padding;
687 padding: @input-padding;
688 border: @input-border-thickness solid @border-highlight-color;
688 border: @input-border-thickness solid @border-highlight-color;
689 .border-radius (@border-radius);
689 .border-radius (@border-radius);
690 font-family: @text-light;
690 font-family: @text-light;
691 font-size: @basefontsize;
691 font-size: @basefontsize;
692
692
693 &.input-sm {
693 &.input-sm {
694 padding: 5px;
694 padding: 5px;
695 }
695 }
696
696
697 &#description {
697 &#description {
698 min-width: @input-description-minwidth;
698 min-width: @input-description-minwidth;
699 min-height: 1em;
699 min-height: 1em;
700 padding: 10px;
700 padding: 10px;
701 }
701 }
702 }
702 }
703
703
704 .field-sm {
704 .field-sm {
705 input,
705 input,
706 textarea {
706 textarea {
707 padding: 5px;
707 padding: 5px;
708 }
708 }
709 }
709 }
710
710
711 textarea {
711 textarea {
712 display: block;
712 display: block;
713 clear: both;
713 clear: both;
714 width: 100%;
714 width: 100%;
715 min-height: 100px;
715 min-height: 100px;
716 margin-bottom: @padding;
716 margin-bottom: @padding;
717 .box-sizing(border-box);
717 .box-sizing(border-box);
718 overflow: auto;
718 overflow: auto;
719 }
719 }
720
720
721 label {
721 label {
722 font-family: @text-light;
722 font-family: @text-light;
723 }
723 }
724
724
725 // GRAVATARS
725 // GRAVATARS
726 // centers gravatar on username to the right
726 // centers gravatar on username to the right
727
727
728 .gravatar {
728 .gravatar {
729 display: inline;
729 display: inline;
730 min-width: 16px;
730 min-width: 16px;
731 min-height: 16px;
731 min-height: 16px;
732 margin: -5px 0;
732 margin: -5px 0;
733 padding: 0;
733 padding: 0;
734 line-height: 1em;
734 line-height: 1em;
735 box-sizing: content-box;
735 box-sizing: content-box;
736 border-radius: 50%;
736 border-radius: 50%;
737
737
738 &.gravatar-large {
738 &.gravatar-large {
739 margin: -0.5em .25em -0.5em 0;
739 margin: -0.5em .25em -0.5em 0;
740 }
740 }
741
741
742 & + .user {
742 & + .user {
743 display: inline;
743 display: inline;
744 margin: 0;
744 margin: 0;
745 padding: 0 0 0 .17em;
745 padding: 0 0 0 .17em;
746 line-height: 1em;
746 line-height: 1em;
747 }
747 }
748 }
748 }
749
749
750 .user-inline-data {
750 .user-inline-data {
751 display: inline-block;
751 display: inline-block;
752 float: left;
752 float: left;
753 padding-left: .5em;
753 padding-left: .5em;
754 line-height: 1.3em;
754 line-height: 1.3em;
755 }
755 }
756
756
757 .rc-user { // gravatar + user wrapper
757 .rc-user { // gravatar + user wrapper
758 float: left;
758 float: left;
759 position: relative;
759 position: relative;
760 min-width: 100px;
760 min-width: 100px;
761 max-width: 200px;
761 max-width: 200px;
762 min-height: (@gravatar-size + @border-thickness * 2); // account for border
762 min-height: (@gravatar-size + @border-thickness * 2); // account for border
763 display: block;
763 display: block;
764 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
764 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
765
765
766
766
767 .gravatar {
767 .gravatar {
768 display: block;
768 display: block;
769 position: absolute;
769 position: absolute;
770 top: 0;
770 top: 0;
771 left: 0;
771 left: 0;
772 min-width: @gravatar-size;
772 min-width: @gravatar-size;
773 min-height: @gravatar-size;
773 min-height: @gravatar-size;
774 margin: 0;
774 margin: 0;
775 }
775 }
776
776
777 .user {
777 .user {
778 display: block;
778 display: block;
779 max-width: 175px;
779 max-width: 175px;
780 padding-top: 2px;
780 padding-top: 2px;
781 overflow: hidden;
781 overflow: hidden;
782 text-overflow: ellipsis;
782 text-overflow: ellipsis;
783 }
783 }
784 }
784 }
785
785
786 .gist-gravatar,
786 .gist-gravatar,
787 .journal_container {
787 .journal_container {
788 .gravatar-large {
788 .gravatar-large {
789 margin: 0 .5em -10px 0;
789 margin: 0 .5em -10px 0;
790 }
790 }
791 }
791 }
792
792
793
793
794 // ADMIN SETTINGS
794 // ADMIN SETTINGS
795
795
796 // Tag Patterns
796 // Tag Patterns
797 .tag_patterns {
797 .tag_patterns {
798 .tag_input {
798 .tag_input {
799 margin-bottom: @padding;
799 margin-bottom: @padding;
800 }
800 }
801 }
801 }
802
802
803 .locked_input {
803 .locked_input {
804 position: relative;
804 position: relative;
805
805
806 input {
806 input {
807 display: inline;
807 display: inline;
808 margin: 3px 5px 0px 0px;
808 margin: 3px 5px 0px 0px;
809 }
809 }
810
810
811 br {
811 br {
812 display: none;
812 display: none;
813 }
813 }
814
814
815 .error-message {
815 .error-message {
816 float: left;
816 float: left;
817 width: 100%;
817 width: 100%;
818 }
818 }
819
819
820 .lock_input_button {
820 .lock_input_button {
821 display: inline;
821 display: inline;
822 }
822 }
823
823
824 .help-block {
824 .help-block {
825 clear: both;
825 clear: both;
826 }
826 }
827 }
827 }
828
828
829 // Notifications
829 // Notifications
830
830
831 .notifications_buttons {
831 .notifications_buttons {
832 margin: 0 0 @space 0;
832 margin: 0 0 @space 0;
833 padding: 0;
833 padding: 0;
834
834
835 .btn {
835 .btn {
836 display: inline-block;
836 display: inline-block;
837 }
837 }
838 }
838 }
839
839
840 .notification-list {
840 .notification-list {
841
841
842 div {
842 div {
843 display: inline-block;
843 display: inline-block;
844 vertical-align: middle;
844 vertical-align: middle;
845 }
845 }
846
846
847 .container {
847 .container {
848 display: block;
848 display: block;
849 margin: 0 0 @padding 0;
849 margin: 0 0 @padding 0;
850 }
850 }
851
851
852 .delete-notifications {
852 .delete-notifications {
853 margin-left: @padding;
853 margin-left: @padding;
854 text-align: right;
854 text-align: right;
855 cursor: pointer;
855 cursor: pointer;
856 }
856 }
857
857
858 .read-notifications {
858 .read-notifications {
859 margin-left: @padding/2;
859 margin-left: @padding/2;
860 text-align: right;
860 text-align: right;
861 width: 35px;
861 width: 35px;
862 cursor: pointer;
862 cursor: pointer;
863 }
863 }
864
864
865 .icon-minus-sign {
865 .icon-minus-sign {
866 color: @alert2;
866 color: @alert2;
867 }
867 }
868
868
869 .icon-ok-sign {
869 .icon-ok-sign {
870 color: @alert1;
870 color: @alert1;
871 }
871 }
872 }
872 }
873
873
874 .user_settings {
874 .user_settings {
875 float: left;
875 float: left;
876 clear: both;
876 clear: both;
877 display: block;
877 display: block;
878 width: 100%;
878 width: 100%;
879
879
880 .gravatar_box {
880 .gravatar_box {
881 margin-bottom: @padding;
881 margin-bottom: @padding;
882
882
883 &:after {
883 &:after {
884 content: " ";
884 content: " ";
885 clear: both;
885 clear: both;
886 width: 100%;
886 width: 100%;
887 }
887 }
888 }
888 }
889
889
890 .fields .field {
890 .fields .field {
891 clear: both;
891 clear: both;
892 }
892 }
893 }
893 }
894
894
895 .advanced_settings {
895 .advanced_settings {
896 margin-bottom: @space;
896 margin-bottom: @space;
897
897
898 .help-block {
898 .help-block {
899 margin-left: 0;
899 margin-left: 0;
900 }
900 }
901
901
902 button + .help-block {
902 button + .help-block {
903 margin-top: @padding;
903 margin-top: @padding;
904 }
904 }
905 }
905 }
906
906
907 // admin settings radio buttons and labels
907 // admin settings radio buttons and labels
908 .label-2 {
908 .label-2 {
909 float: left;
909 float: left;
910 width: @label2-width;
910 width: @label2-width;
911
911
912 label {
912 label {
913 color: @grey1;
913 color: @grey1;
914 }
914 }
915 }
915 }
916 .checkboxes {
916 .checkboxes {
917 float: left;
917 float: left;
918 width: @checkboxes-width;
918 width: @checkboxes-width;
919 margin-bottom: @padding;
919 margin-bottom: @padding;
920
920
921 .checkbox {
921 .checkbox {
922 width: 100%;
922 width: 100%;
923
923
924 label {
924 label {
925 margin: 0;
925 margin: 0;
926 padding: 0;
926 padding: 0;
927 }
927 }
928 }
928 }
929
929
930 .checkbox + .checkbox {
930 .checkbox + .checkbox {
931 display: inline-block;
931 display: inline-block;
932 }
932 }
933
933
934 label {
934 label {
935 margin-right: 1em;
935 margin-right: 1em;
936 }
936 }
937 }
937 }
938
938
939 // CHANGELOG
939 // CHANGELOG
940 .container_header {
940 .container_header {
941 float: left;
941 float: left;
942 display: block;
942 display: block;
943 width: 100%;
943 width: 100%;
944 margin: @padding 0 @padding;
944 margin: @padding 0 @padding;
945
945
946 #filter_changelog {
946 #filter_changelog {
947 float: left;
947 float: left;
948 margin-right: @padding;
948 margin-right: @padding;
949 }
949 }
950
950
951 .breadcrumbs_light {
951 .breadcrumbs_light {
952 display: inline-block;
952 display: inline-block;
953 }
953 }
954 }
954 }
955
955
956 .info_box {
956 .info_box {
957 float: right;
957 float: right;
958 }
958 }
959
959
960
960
961 #graph_nodes {
961 #graph_nodes {
962 padding-top: 43px;
962 padding-top: 43px;
963 }
963 }
964
964
965 #graph_content{
965 #graph_content{
966
966
967 // adjust for table headers so that graph renders properly
967 // adjust for table headers so that graph renders properly
968 // #graph_nodes padding - table cell padding
968 // #graph_nodes padding - table cell padding
969 padding-top: (@space - (@basefontsize * 2.4));
969 padding-top: (@space - (@basefontsize * 2.4));
970
970
971 &.graph_full_width {
971 &.graph_full_width {
972 width: 100%;
972 width: 100%;
973 max-width: 100%;
973 max-width: 100%;
974 }
974 }
975 }
975 }
976
976
977 #graph {
977 #graph {
978 .flag_status {
978 .flag_status {
979 margin: 0;
979 margin: 0;
980 }
980 }
981
981
982 .pagination-left {
982 .pagination-left {
983 float: left;
983 float: left;
984 clear: both;
984 clear: both;
985 }
985 }
986
986
987 .log-container {
987 .log-container {
988 max-width: 345px;
988 max-width: 345px;
989
989
990 .message{
990 .message{
991 max-width: 340px;
991 max-width: 340px;
992 }
992 }
993 }
993 }
994
994
995 .graph-col-wrapper {
995 .graph-col-wrapper {
996 padding-left: 110px;
996 padding-left: 110px;
997
997
998 #graph_nodes {
998 #graph_nodes {
999 width: 100px;
999 width: 100px;
1000 margin-left: -110px;
1000 margin-left: -110px;
1001 float: left;
1001 float: left;
1002 clear: left;
1002 clear: left;
1003 }
1003 }
1004 }
1004 }
1005
1005
1006 .load-more-commits {
1006 .load-more-commits {
1007 text-align: center;
1007 text-align: center;
1008 }
1008 }
1009 .load-more-commits:hover {
1009 .load-more-commits:hover {
1010 background-color: @grey7;
1010 background-color: @grey7;
1011 }
1011 }
1012 .load-more-commits {
1012 .load-more-commits {
1013 a {
1013 a {
1014 display: block;
1014 display: block;
1015 }
1015 }
1016 }
1016 }
1017 }
1017 }
1018
1018
1019 #filter_changelog {
1019 #filter_changelog {
1020 float: left;
1020 float: left;
1021 }
1021 }
1022
1022
1023
1023
1024 //--- THEME ------------------//
1024 //--- THEME ------------------//
1025
1025
1026 #logo {
1026 #logo {
1027 float: left;
1027 float: left;
1028 margin: 9px 0 0 0;
1028 margin: 9px 0 0 0;
1029
1029
1030 .header {
1030 .header {
1031 background-color: transparent;
1031 background-color: transparent;
1032 }
1032 }
1033
1033
1034 a {
1034 a {
1035 display: inline-block;
1035 display: inline-block;
1036 }
1036 }
1037
1037
1038 img {
1038 img {
1039 height:30px;
1039 height:30px;
1040 }
1040 }
1041 }
1041 }
1042
1042
1043 .logo-wrapper {
1043 .logo-wrapper {
1044 float:left;
1044 float:left;
1045 }
1045 }
1046
1046
1047 .branding {
1047 .branding {
1048 float: left;
1048 float: left;
1049 padding: 9px 2px;
1049 padding: 9px 2px;
1050 line-height: 1em;
1050 line-height: 1em;
1051 font-size: @navigation-fontsize;
1051 font-size: @navigation-fontsize;
1052
1052
1053 a {
1053 a {
1054 color: @grey5
1054 color: @grey5
1055 }
1055 }
1056 }
1056 }
1057
1057
1058 img {
1058 img {
1059 border: none;
1059 border: none;
1060 outline: none;
1060 outline: none;
1061 }
1061 }
1062 user-profile-header
1062 user-profile-header
1063 label {
1063 label {
1064
1064
1065 input[type="checkbox"] {
1065 input[type="checkbox"] {
1066 margin-right: 1em;
1066 margin-right: 1em;
1067 }
1067 }
1068 input[type="radio"] {
1068 input[type="radio"] {
1069 margin-right: 1em;
1069 margin-right: 1em;
1070 }
1070 }
1071 }
1071 }
1072
1072
1073 .flag_status {
1073 .flag_status {
1074 margin: 2px;
1074 margin: 2px;
1075 &.under_review {
1075 &.under_review {
1076 .circle(5px, @alert3);
1076 .circle(5px, @alert3);
1077 }
1077 }
1078 &.approved {
1078 &.approved {
1079 .circle(5px, @alert1);
1079 .circle(5px, @alert1);
1080 }
1080 }
1081 &.rejected,
1081 &.rejected,
1082 &.forced_closed{
1082 &.forced_closed{
1083 .circle(5px, @alert2);
1083 .circle(5px, @alert2);
1084 }
1084 }
1085 &.not_reviewed {
1085 &.not_reviewed {
1086 .circle(5px, @grey5);
1086 .circle(5px, @grey5);
1087 }
1087 }
1088 }
1088 }
1089
1089
1090 .flag_status_comment_box {
1090 .flag_status_comment_box {
1091 margin: 5px 6px 0px 2px;
1091 margin: 5px 6px 0px 2px;
1092 }
1092 }
1093 .test_pattern_preview {
1093 .test_pattern_preview {
1094 margin: @space 0;
1094 margin: @space 0;
1095
1095
1096 p {
1096 p {
1097 margin-bottom: 0;
1097 margin-bottom: 0;
1098 border-bottom: @border-thickness solid @border-default-color;
1098 border-bottom: @border-thickness solid @border-default-color;
1099 color: @grey3;
1099 color: @grey3;
1100 }
1100 }
1101
1101
1102 .btn {
1102 .btn {
1103 margin-bottom: @padding;
1103 margin-bottom: @padding;
1104 }
1104 }
1105 }
1105 }
1106 #test_pattern_result {
1106 #test_pattern_result {
1107 display: none;
1107 display: none;
1108 &:extend(pre);
1108 &:extend(pre);
1109 padding: .9em;
1109 padding: .9em;
1110 color: @grey3;
1110 color: @grey3;
1111 background-color: @grey7;
1111 background-color: @grey7;
1112 border-right: @border-thickness solid @border-default-color;
1112 border-right: @border-thickness solid @border-default-color;
1113 border-bottom: @border-thickness solid @border-default-color;
1113 border-bottom: @border-thickness solid @border-default-color;
1114 border-left: @border-thickness solid @border-default-color;
1114 border-left: @border-thickness solid @border-default-color;
1115 }
1115 }
1116
1116
1117 #repo_vcs_settings {
1117 #repo_vcs_settings {
1118 #inherit_overlay_vcs_default {
1118 #inherit_overlay_vcs_default {
1119 display: none;
1119 display: none;
1120 }
1120 }
1121 #inherit_overlay_vcs_custom {
1121 #inherit_overlay_vcs_custom {
1122 display: custom;
1122 display: custom;
1123 }
1123 }
1124 &.inherited {
1124 &.inherited {
1125 #inherit_overlay_vcs_default {
1125 #inherit_overlay_vcs_default {
1126 display: block;
1126 display: block;
1127 }
1127 }
1128 #inherit_overlay_vcs_custom {
1128 #inherit_overlay_vcs_custom {
1129 display: none;
1129 display: none;
1130 }
1130 }
1131 }
1131 }
1132 }
1132 }
1133
1133
1134 .issue-tracker-link {
1134 .issue-tracker-link {
1135 color: @rcblue;
1135 color: @rcblue;
1136 }
1136 }
1137
1137
1138 // Issue Tracker Table Show/Hide
1138 // Issue Tracker Table Show/Hide
1139 #repo_issue_tracker {
1139 #repo_issue_tracker {
1140 #inherit_overlay {
1140 #inherit_overlay {
1141 display: none;
1141 display: none;
1142 }
1142 }
1143 #custom_overlay {
1143 #custom_overlay {
1144 display: custom;
1144 display: custom;
1145 }
1145 }
1146 &.inherited {
1146 &.inherited {
1147 #inherit_overlay {
1147 #inherit_overlay {
1148 display: block;
1148 display: block;
1149 }
1149 }
1150 #custom_overlay {
1150 #custom_overlay {
1151 display: none;
1151 display: none;
1152 }
1152 }
1153 }
1153 }
1154 }
1154 }
1155 table.issuetracker {
1155 table.issuetracker {
1156 &.readonly {
1156 &.readonly {
1157 tr, td {
1157 tr, td {
1158 color: @grey3;
1158 color: @grey3;
1159 }
1159 }
1160 }
1160 }
1161 .edit {
1161 .edit {
1162 display: none;
1162 display: none;
1163 }
1163 }
1164 .editopen {
1164 .editopen {
1165 .edit {
1165 .edit {
1166 display: inline;
1166 display: inline;
1167 }
1167 }
1168 .entry {
1168 .entry {
1169 display: none;
1169 display: none;
1170 }
1170 }
1171 }
1171 }
1172 tr td.td-action {
1172 tr td.td-action {
1173 min-width: 117px;
1173 min-width: 117px;
1174 }
1174 }
1175 td input {
1175 td input {
1176 max-width: none;
1176 max-width: none;
1177 min-width: 30px;
1177 min-width: 30px;
1178 width: 80%;
1178 width: 80%;
1179 }
1179 }
1180 .issuetracker_pref input {
1180 .issuetracker_pref input {
1181 width: 40%;
1181 width: 40%;
1182 }
1182 }
1183 input.edit_issuetracker_update {
1183 input.edit_issuetracker_update {
1184 margin-right: 0;
1184 margin-right: 0;
1185 width: auto;
1185 width: auto;
1186 }
1186 }
1187 }
1187 }
1188
1188
1189 table.integrations {
1189 table.integrations {
1190 .td-icon {
1190 .td-icon {
1191 width: 20px;
1191 width: 20px;
1192 .integration-icon {
1192 .integration-icon {
1193 height: 20px;
1193 height: 20px;
1194 width: 20px;
1194 width: 20px;
1195 }
1195 }
1196 }
1196 }
1197 }
1197 }
1198
1198
1199 .integrations {
1199 .integrations {
1200 a.integration-box {
1200 a.integration-box {
1201 color: @text-color;
1201 color: @text-color;
1202 &:hover {
1202 &:hover {
1203 .panel {
1203 .panel {
1204 background: #fbfbfb;
1204 background: #fbfbfb;
1205 }
1205 }
1206 }
1206 }
1207 .integration-icon {
1207 .integration-icon {
1208 width: 30px;
1208 width: 30px;
1209 height: 30px;
1209 height: 30px;
1210 margin-right: 20px;
1210 margin-right: 20px;
1211 float: left;
1211 float: left;
1212 }
1212 }
1213
1213
1214 .panel-body {
1214 .panel-body {
1215 padding: 10px;
1215 padding: 10px;
1216 }
1216 }
1217 .panel {
1217 .panel {
1218 margin-bottom: 10px;
1218 margin-bottom: 10px;
1219 }
1219 }
1220 h2 {
1220 h2 {
1221 display: inline-block;
1221 display: inline-block;
1222 margin: 0;
1222 margin: 0;
1223 min-width: 140px;
1223 min-width: 140px;
1224 }
1224 }
1225 }
1225 }
1226 a.integration-box.dummy-integration {
1226 a.integration-box.dummy-integration {
1227 color: @grey4
1227 color: @grey4
1228 }
1228 }
1229 }
1229 }
1230
1230
1231 //Permissions Settings
1231 //Permissions Settings
1232 #add_perm {
1232 #add_perm {
1233 margin: 0 0 @padding;
1233 margin: 0 0 @padding;
1234 cursor: pointer;
1234 cursor: pointer;
1235 }
1235 }
1236
1236
1237 .perm_ac {
1237 .perm_ac {
1238 input {
1238 input {
1239 width: 95%;
1239 width: 95%;
1240 }
1240 }
1241 }
1241 }
1242
1242
1243 .autocomplete-suggestions {
1243 .autocomplete-suggestions {
1244 width: auto !important; // overrides autocomplete.js
1244 width: auto !important; // overrides autocomplete.js
1245 min-width: 278px;
1245 min-width: 278px;
1246 margin: 0;
1246 margin: 0;
1247 border: @border-thickness solid @grey5;
1247 border: @border-thickness solid @grey5;
1248 border-radius: @border-radius;
1248 border-radius: @border-radius;
1249 color: @grey2;
1249 color: @grey2;
1250 background-color: white;
1250 background-color: white;
1251 }
1251 }
1252
1252
1253 .autocomplete-selected {
1253 .autocomplete-selected {
1254 background: #F0F0F0;
1254 background: #F0F0F0;
1255 }
1255 }
1256
1256
1257 .ac-container-wrap {
1257 .ac-container-wrap {
1258 margin: 0;
1258 margin: 0;
1259 padding: 8px;
1259 padding: 8px;
1260 border-bottom: @border-thickness solid @grey5;
1260 border-bottom: @border-thickness solid @grey5;
1261 list-style-type: none;
1261 list-style-type: none;
1262 cursor: pointer;
1262 cursor: pointer;
1263
1263
1264 &:hover {
1264 &:hover {
1265 background-color: @grey7;
1265 background-color: @grey7;
1266 }
1266 }
1267
1267
1268 img {
1268 img {
1269 height: @gravatar-size;
1269 height: @gravatar-size;
1270 width: @gravatar-size;
1270 width: @gravatar-size;
1271 margin-right: 1em;
1271 margin-right: 1em;
1272 }
1272 }
1273
1273
1274 strong {
1274 strong {
1275 font-weight: normal;
1275 font-weight: normal;
1276 }
1276 }
1277 }
1277 }
1278
1278
1279 // Settings Dropdown
1279 // Settings Dropdown
1280 .user-menu .container {
1280 .user-menu .container {
1281 padding: 0 4px;
1281 padding: 0 4px;
1282 margin: 0;
1282 margin: 0;
1283 }
1283 }
1284
1284
1285 .user-menu .gravatar {
1285 .user-menu .gravatar {
1286 cursor: pointer;
1286 cursor: pointer;
1287 }
1287 }
1288
1288
1289 .codeblock {
1289 .codeblock {
1290 margin-bottom: @padding;
1290 margin-bottom: @padding;
1291 clear: both;
1291 clear: both;
1292
1292
1293 .stats {
1293 .stats {
1294 overflow: hidden;
1294 overflow: hidden;
1295 }
1295 }
1296
1296
1297 .message{
1297 .message{
1298 textarea{
1298 textarea{
1299 margin: 0;
1299 margin: 0;
1300 }
1300 }
1301 }
1301 }
1302
1302
1303 .code-header {
1303 .code-header {
1304 .stats {
1304 .stats {
1305 line-height: 2em;
1305 line-height: 2em;
1306
1306
1307 .revision_id {
1307 .revision_id {
1308 margin-left: 0;
1308 margin-left: 0;
1309 }
1309 }
1310 .buttons {
1310 .buttons {
1311 padding-right: 0;
1311 padding-right: 0;
1312 }
1312 }
1313 }
1313 }
1314
1314
1315 .item{
1315 .item{
1316 margin-right: 0.5em;
1316 margin-right: 0.5em;
1317 }
1317 }
1318 }
1318 }
1319
1319
1320 #editor_container{
1320 #editor_container{
1321 position: relative;
1321 position: relative;
1322 margin: @padding;
1322 margin: @padding;
1323 }
1323 }
1324 }
1324 }
1325
1325
1326 #file_history_container {
1326 #file_history_container {
1327 display: none;
1327 display: none;
1328 }
1328 }
1329
1329
1330 .file-history-inner {
1330 .file-history-inner {
1331 margin-bottom: 10px;
1331 margin-bottom: 10px;
1332 }
1332 }
1333
1333
1334 // Pull Requests
1334 // Pull Requests
1335 .summary-details {
1335 .summary-details {
1336 width: 72%;
1336 width: 72%;
1337 }
1337 }
1338 .pr-summary {
1338 .pr-summary {
1339 border-bottom: @border-thickness solid @grey5;
1339 border-bottom: @border-thickness solid @grey5;
1340 margin-bottom: @space;
1340 margin-bottom: @space;
1341 }
1341 }
1342 .reviewers-title {
1342 .reviewers-title {
1343 width: 25%;
1343 width: 25%;
1344 min-width: 200px;
1344 min-width: 200px;
1345 }
1345 }
1346 .reviewers {
1346 .reviewers {
1347 width: 25%;
1347 width: 25%;
1348 min-width: 200px;
1348 min-width: 200px;
1349 }
1349 }
1350 .reviewers ul li {
1350 .reviewers ul li {
1351 position: relative;
1351 position: relative;
1352 width: 100%;
1352 width: 100%;
1353 padding-bottom: 8px;
1353 padding-bottom: 8px;
1354 list-style-type: none;
1354 list-style-type: none;
1355 }
1355 }
1356
1356
1357 .reviewer_entry {
1357 .reviewer_entry {
1358 min-height: 55px;
1358 min-height: 55px;
1359 }
1359 }
1360
1360
1361 .reviewers_member {
1361 .reviewers_member {
1362 width: 100%;
1362 width: 100%;
1363 overflow: auto;
1363 overflow: auto;
1364 }
1364 }
1365 .reviewer_reason {
1365 .reviewer_reason {
1366 padding-left: 20px;
1366 padding-left: 20px;
1367 line-height: 1.5em;
1367 line-height: 1.5em;
1368 }
1368 }
1369 .reviewer_status {
1369 .reviewer_status {
1370 display: inline-block;
1370 display: inline-block;
1371 vertical-align: top;
1371 vertical-align: top;
1372 width: 25px;
1372 width: 25px;
1373 min-width: 25px;
1373 min-width: 25px;
1374 height: 1.2em;
1374 height: 1.2em;
1375 margin-top: 3px;
1375 margin-top: 3px;
1376 line-height: 1em;
1376 line-height: 1em;
1377 }
1377 }
1378
1378
1379 .reviewer_name {
1379 .reviewer_name {
1380 display: inline-block;
1380 display: inline-block;
1381 max-width: 83%;
1381 max-width: 83%;
1382 padding-right: 20px;
1382 padding-right: 20px;
1383 vertical-align: middle;
1383 vertical-align: middle;
1384 line-height: 1;
1384 line-height: 1;
1385
1385
1386 .rc-user {
1386 .rc-user {
1387 min-width: 0;
1387 min-width: 0;
1388 margin: -2px 1em 0 0;
1388 margin: -2px 1em 0 0;
1389 }
1389 }
1390
1390
1391 .reviewer {
1391 .reviewer {
1392 float: left;
1392 float: left;
1393 }
1393 }
1394 }
1394 }
1395
1395
1396 .reviewer_member_mandatory {
1396 .reviewer_member_mandatory {
1397 position: absolute;
1397 position: absolute;
1398 left: 15px;
1398 left: 15px;
1399 top: 8px;
1399 top: 8px;
1400 width: 16px;
1400 width: 16px;
1401 font-size: 11px;
1401 font-size: 11px;
1402 margin: 0;
1402 margin: 0;
1403 padding: 0;
1403 padding: 0;
1404 color: black;
1404 color: black;
1405 }
1405 }
1406
1406
1407 .reviewer_member_mandatory_remove,
1407 .reviewer_member_mandatory_remove,
1408 .reviewer_member_remove {
1408 .reviewer_member_remove {
1409 position: absolute;
1409 position: absolute;
1410 right: 0;
1410 right: 0;
1411 top: 0;
1411 top: 0;
1412 width: 16px;
1412 width: 16px;
1413 margin-bottom: 10px;
1413 margin-bottom: 10px;
1414 padding: 0;
1414 padding: 0;
1415 color: black;
1415 color: black;
1416 }
1416 }
1417
1417
1418 .reviewer_member_mandatory_remove {
1418 .reviewer_member_mandatory_remove {
1419 color: @grey4;
1419 color: @grey4;
1420 }
1420 }
1421
1421
1422 .reviewer_member_status {
1422 .reviewer_member_status {
1423 margin-top: 5px;
1423 margin-top: 5px;
1424 }
1424 }
1425 .pr-summary #summary{
1425 .pr-summary #summary{
1426 width: 100%;
1426 width: 100%;
1427 }
1427 }
1428 .pr-summary .action_button:hover {
1428 .pr-summary .action_button:hover {
1429 border: 0;
1429 border: 0;
1430 cursor: pointer;
1430 cursor: pointer;
1431 }
1431 }
1432 .pr-details-title {
1432 .pr-details-title {
1433 padding-bottom: 8px;
1433 padding-bottom: 8px;
1434 border-bottom: @border-thickness solid @grey5;
1434 border-bottom: @border-thickness solid @grey5;
1435
1435
1436 .action_button.disabled {
1436 .action_button.disabled {
1437 color: @grey4;
1437 color: @grey4;
1438 cursor: inherit;
1438 cursor: inherit;
1439 }
1439 }
1440 .action_button {
1440 .action_button {
1441 color: @rcblue;
1441 color: @rcblue;
1442 }
1442 }
1443 }
1443 }
1444 .pr-details-content {
1444 .pr-details-content {
1445 margin-top: @textmargin;
1445 margin-top: @textmargin;
1446 margin-bottom: @textmargin;
1446 margin-bottom: @textmargin;
1447 }
1447 }
1448
1448
1449 .pr-reviewer-rules {
1449 .pr-reviewer-rules {
1450 padding: 10px 0px 20px 0px;
1450 padding: 10px 0px 20px 0px;
1451 }
1451 }
1452
1452
1453 .group_members {
1453 .group_members {
1454 margin-top: 0;
1454 margin-top: 0;
1455 padding: 0;
1455 padding: 0;
1456 list-style: outside none none;
1456 list-style: outside none none;
1457
1457
1458 img {
1458 img {
1459 height: @gravatar-size;
1459 height: @gravatar-size;
1460 width: @gravatar-size;
1460 width: @gravatar-size;
1461 margin-right: .5em;
1461 margin-right: .5em;
1462 margin-left: 3px;
1462 margin-left: 3px;
1463 }
1463 }
1464
1464
1465 .to-delete {
1465 .to-delete {
1466 .user {
1466 .user {
1467 text-decoration: line-through;
1467 text-decoration: line-through;
1468 }
1468 }
1469 }
1469 }
1470 }
1470 }
1471
1471
1472 .compare_view_commits_title {
1472 .compare_view_commits_title {
1473 .disabled {
1473 .disabled {
1474 cursor: inherit;
1474 cursor: inherit;
1475 &:hover{
1475 &:hover{
1476 background-color: inherit;
1476 background-color: inherit;
1477 color: inherit;
1477 color: inherit;
1478 }
1478 }
1479 }
1479 }
1480 }
1480 }
1481
1481
1482 .subtitle-compare {
1482 .subtitle-compare {
1483 margin: -15px 0px 0px 0px;
1483 margin: -15px 0px 0px 0px;
1484 }
1484 }
1485
1485
1486 .comments-summary-td {
1486 .comments-summary-td {
1487 border-top: 1px dashed @grey5;
1487 border-top: 1px dashed @grey5;
1488 }
1488 }
1489
1489
1490 // new entry in group_members
1490 // new entry in group_members
1491 .td-author-new-entry {
1491 .td-author-new-entry {
1492 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1492 background-color: rgba(red(@alert1), green(@alert1), blue(@alert1), 0.3);
1493 }
1493 }
1494
1494
1495 .usergroup_member_remove {
1495 .usergroup_member_remove {
1496 width: 16px;
1496 width: 16px;
1497 margin-bottom: 10px;
1497 margin-bottom: 10px;
1498 padding: 0;
1498 padding: 0;
1499 color: black !important;
1499 color: black !important;
1500 cursor: pointer;
1500 cursor: pointer;
1501 }
1501 }
1502
1502
1503 .reviewer_ac .ac-input {
1503 .reviewer_ac .ac-input {
1504 width: 92%;
1504 width: 92%;
1505 margin-bottom: 1em;
1505 margin-bottom: 1em;
1506 }
1506 }
1507
1507
1508 .compare_view_commits tr{
1508 .compare_view_commits tr{
1509 height: 20px;
1509 height: 20px;
1510 }
1510 }
1511 .compare_view_commits td {
1511 .compare_view_commits td {
1512 vertical-align: top;
1512 vertical-align: top;
1513 padding-top: 10px;
1513 padding-top: 10px;
1514 }
1514 }
1515 .compare_view_commits .author {
1515 .compare_view_commits .author {
1516 margin-left: 5px;
1516 margin-left: 5px;
1517 }
1517 }
1518
1518
1519 .compare_view_commits {
1519 .compare_view_commits {
1520 .color-a {
1520 .color-a {
1521 color: @alert1;
1521 color: @alert1;
1522 }
1522 }
1523
1523
1524 .color-c {
1524 .color-c {
1525 color: @color3;
1525 color: @color3;
1526 }
1526 }
1527
1527
1528 .color-r {
1528 .color-r {
1529 color: @color5;
1529 color: @color5;
1530 }
1530 }
1531
1531
1532 .color-a-bg {
1532 .color-a-bg {
1533 background-color: @alert1;
1533 background-color: @alert1;
1534 }
1534 }
1535
1535
1536 .color-c-bg {
1536 .color-c-bg {
1537 background-color: @alert3;
1537 background-color: @alert3;
1538 }
1538 }
1539
1539
1540 .color-r-bg {
1540 .color-r-bg {
1541 background-color: @alert2;
1541 background-color: @alert2;
1542 }
1542 }
1543
1543
1544 .color-a-border {
1544 .color-a-border {
1545 border: 1px solid @alert1;
1545 border: 1px solid @alert1;
1546 }
1546 }
1547
1547
1548 .color-c-border {
1548 .color-c-border {
1549 border: 1px solid @alert3;
1549 border: 1px solid @alert3;
1550 }
1550 }
1551
1551
1552 .color-r-border {
1552 .color-r-border {
1553 border: 1px solid @alert2;
1553 border: 1px solid @alert2;
1554 }
1554 }
1555
1555
1556 .commit-change-indicator {
1556 .commit-change-indicator {
1557 width: 15px;
1557 width: 15px;
1558 height: 15px;
1558 height: 15px;
1559 position: relative;
1559 position: relative;
1560 left: 15px;
1560 left: 15px;
1561 }
1561 }
1562
1562
1563 .commit-change-content {
1563 .commit-change-content {
1564 text-align: center;
1564 text-align: center;
1565 vertical-align: middle;
1565 vertical-align: middle;
1566 line-height: 15px;
1566 line-height: 15px;
1567 }
1567 }
1568 }
1568 }
1569
1569
1570 .compare_view_filepath {
1570 .compare_view_filepath {
1571 color: @grey1;
1571 color: @grey1;
1572 }
1572 }
1573
1573
1574 .show_more {
1574 .show_more {
1575 display: inline-block;
1575 display: inline-block;
1576 width: 0;
1576 width: 0;
1577 height: 0;
1577 height: 0;
1578 vertical-align: middle;
1578 vertical-align: middle;
1579 content: "";
1579 content: "";
1580 border: 4px solid;
1580 border: 4px solid;
1581 border-right-color: transparent;
1581 border-right-color: transparent;
1582 border-bottom-color: transparent;
1582 border-bottom-color: transparent;
1583 border-left-color: transparent;
1583 border-left-color: transparent;
1584 font-size: 0;
1584 font-size: 0;
1585 }
1585 }
1586
1586
1587 .journal_more .show_more {
1587 .journal_more .show_more {
1588 display: inline;
1588 display: inline;
1589
1589
1590 &:after {
1590 &:after {
1591 content: none;
1591 content: none;
1592 }
1592 }
1593 }
1593 }
1594
1594
1595 .compare_view_commits .collapse_commit:after {
1595 .compare_view_commits .collapse_commit:after {
1596 cursor: pointer;
1596 cursor: pointer;
1597 content: "\00A0\25B4";
1597 content: "\00A0\25B4";
1598 margin-left: -3px;
1598 margin-left: -3px;
1599 font-size: 17px;
1599 font-size: 17px;
1600 color: @grey4;
1600 color: @grey4;
1601 }
1601 }
1602
1602
1603 .diff_links {
1603 .diff_links {
1604 margin-left: 8px;
1604 margin-left: 8px;
1605 }
1605 }
1606
1606
1607 div.ancestor {
1607 div.ancestor {
1608 margin: -30px 0px;
1608 margin: -30px 0px;
1609 }
1609 }
1610
1610
1611 .cs_icon_td input[type="checkbox"] {
1611 .cs_icon_td input[type="checkbox"] {
1612 display: none;
1612 display: none;
1613 }
1613 }
1614
1614
1615 .cs_icon_td .expand_file_icon:after {
1615 .cs_icon_td .expand_file_icon:after {
1616 cursor: pointer;
1616 cursor: pointer;
1617 content: "\00A0\25B6";
1617 content: "\00A0\25B6";
1618 font-size: 12px;
1618 font-size: 12px;
1619 color: @grey4;
1619 color: @grey4;
1620 }
1620 }
1621
1621
1622 .cs_icon_td .collapse_file_icon:after {
1622 .cs_icon_td .collapse_file_icon:after {
1623 cursor: pointer;
1623 cursor: pointer;
1624 content: "\00A0\25BC";
1624 content: "\00A0\25BC";
1625 font-size: 12px;
1625 font-size: 12px;
1626 color: @grey4;
1626 color: @grey4;
1627 }
1627 }
1628
1628
1629 /*new binary
1629 /*new binary
1630 NEW_FILENODE = 1
1630 NEW_FILENODE = 1
1631 DEL_FILENODE = 2
1631 DEL_FILENODE = 2
1632 MOD_FILENODE = 3
1632 MOD_FILENODE = 3
1633 RENAMED_FILENODE = 4
1633 RENAMED_FILENODE = 4
1634 COPIED_FILENODE = 5
1634 COPIED_FILENODE = 5
1635 CHMOD_FILENODE = 6
1635 CHMOD_FILENODE = 6
1636 BIN_FILENODE = 7
1636 BIN_FILENODE = 7
1637 */
1637 */
1638 .cs_files_expand {
1638 .cs_files_expand {
1639 font-size: @basefontsize + 5px;
1639 font-size: @basefontsize + 5px;
1640 line-height: 1.8em;
1640 line-height: 1.8em;
1641 float: right;
1641 float: right;
1642 }
1642 }
1643
1643
1644 .cs_files_expand span{
1644 .cs_files_expand span{
1645 color: @rcblue;
1645 color: @rcblue;
1646 cursor: pointer;
1646 cursor: pointer;
1647 }
1647 }
1648 .cs_files {
1648 .cs_files {
1649 clear: both;
1649 clear: both;
1650 padding-bottom: @padding;
1650 padding-bottom: @padding;
1651
1651
1652 .cur_cs {
1652 .cur_cs {
1653 margin: 10px 2px;
1653 margin: 10px 2px;
1654 font-weight: bold;
1654 font-weight: bold;
1655 }
1655 }
1656
1656
1657 .node {
1657 .node {
1658 float: left;
1658 float: left;
1659 }
1659 }
1660
1660
1661 .changes {
1661 .changes {
1662 float: right;
1662 float: right;
1663 color: white;
1663 color: white;
1664 font-size: @basefontsize - 4px;
1664 font-size: @basefontsize - 4px;
1665 margin-top: 4px;
1665 margin-top: 4px;
1666 opacity: 0.6;
1666 opacity: 0.6;
1667 filter: Alpha(opacity=60); /* IE8 and earlier */
1667 filter: Alpha(opacity=60); /* IE8 and earlier */
1668
1668
1669 .added {
1669 .added {
1670 background-color: @alert1;
1670 background-color: @alert1;
1671 float: left;
1671 float: left;
1672 text-align: center;
1672 text-align: center;
1673 }
1673 }
1674
1674
1675 .deleted {
1675 .deleted {
1676 background-color: @alert2;
1676 background-color: @alert2;
1677 float: left;
1677 float: left;
1678 text-align: center;
1678 text-align: center;
1679 }
1679 }
1680
1680
1681 .bin {
1681 .bin {
1682 background-color: @alert1;
1682 background-color: @alert1;
1683 text-align: center;
1683 text-align: center;
1684 }
1684 }
1685
1685
1686 /*new binary*/
1686 /*new binary*/
1687 .bin.bin1 {
1687 .bin.bin1 {
1688 background-color: @alert1;
1688 background-color: @alert1;
1689 text-align: center;
1689 text-align: center;
1690 }
1690 }
1691
1691
1692 /*deleted binary*/
1692 /*deleted binary*/
1693 .bin.bin2 {
1693 .bin.bin2 {
1694 background-color: @alert2;
1694 background-color: @alert2;
1695 text-align: center;
1695 text-align: center;
1696 }
1696 }
1697
1697
1698 /*mod binary*/
1698 /*mod binary*/
1699 .bin.bin3 {
1699 .bin.bin3 {
1700 background-color: @grey2;
1700 background-color: @grey2;
1701 text-align: center;
1701 text-align: center;
1702 }
1702 }
1703
1703
1704 /*rename file*/
1704 /*rename file*/
1705 .bin.bin4 {
1705 .bin.bin4 {
1706 background-color: @alert4;
1706 background-color: @alert4;
1707 text-align: center;
1707 text-align: center;
1708 }
1708 }
1709
1709
1710 /*copied file*/
1710 /*copied file*/
1711 .bin.bin5 {
1711 .bin.bin5 {
1712 background-color: @alert4;
1712 background-color: @alert4;
1713 text-align: center;
1713 text-align: center;
1714 }
1714 }
1715
1715
1716 /*chmod file*/
1716 /*chmod file*/
1717 .bin.bin6 {
1717 .bin.bin6 {
1718 background-color: @grey2;
1718 background-color: @grey2;
1719 text-align: center;
1719 text-align: center;
1720 }
1720 }
1721 }
1721 }
1722 }
1722 }
1723
1723
1724 .cs_files .cs_added, .cs_files .cs_A,
1724 .cs_files .cs_added, .cs_files .cs_A,
1725 .cs_files .cs_added, .cs_files .cs_M,
1725 .cs_files .cs_added, .cs_files .cs_M,
1726 .cs_files .cs_added, .cs_files .cs_D {
1726 .cs_files .cs_added, .cs_files .cs_D {
1727 height: 16px;
1727 height: 16px;
1728 padding-right: 10px;
1728 padding-right: 10px;
1729 margin-top: 7px;
1729 margin-top: 7px;
1730 text-align: left;
1730 text-align: left;
1731 }
1731 }
1732
1732
1733 .cs_icon_td {
1733 .cs_icon_td {
1734 min-width: 16px;
1734 min-width: 16px;
1735 width: 16px;
1735 width: 16px;
1736 }
1736 }
1737
1737
1738 .pull-request-merge {
1738 .pull-request-merge {
1739 border: 1px solid @grey5;
1739 border: 1px solid @grey5;
1740 padding: 10px 0px 20px;
1740 padding: 10px 0px 20px;
1741 margin-top: 10px;
1741 margin-top: 10px;
1742 margin-bottom: 20px;
1742 margin-bottom: 20px;
1743 }
1743 }
1744
1744
1745 .pull-request-merge ul {
1745 .pull-request-merge ul {
1746 padding: 0px 0px;
1746 padding: 0px 0px;
1747 }
1747 }
1748
1748
1749 .pull-request-merge li {
1749 .pull-request-merge li {
1750 list-style-type: none;
1750 list-style-type: none;
1751 }
1751 }
1752
1752
1753 .pull-request-merge .pull-request-wrap {
1753 .pull-request-merge .pull-request-wrap {
1754 height: auto;
1754 height: auto;
1755 padding: 0px 0px;
1755 padding: 0px 0px;
1756 text-align: right;
1756 text-align: right;
1757 }
1757 }
1758
1758
1759 .pull-request-merge span {
1759 .pull-request-merge span {
1760 margin-right: 5px;
1760 margin-right: 5px;
1761 }
1761 }
1762
1762
1763 .pull-request-merge-actions {
1763 .pull-request-merge-actions {
1764 min-height: 30px;
1764 min-height: 30px;
1765 padding: 0px 0px;
1765 padding: 0px 0px;
1766 }
1766 }
1767
1767
1768 .pull-request-merge-info {
1768 .pull-request-merge-info {
1769 padding: 0px 5px 5px 0px;
1769 padding: 0px 5px 5px 0px;
1770 }
1770 }
1771
1771
1772 .merge-status {
1772 .merge-status {
1773 margin-right: 5px;
1773 margin-right: 5px;
1774 }
1774 }
1775
1775
1776 .merge-message {
1776 .merge-message {
1777 font-size: 1.2em
1777 font-size: 1.2em
1778 }
1778 }
1779
1779
1780 .merge-message.success i,
1780 .merge-message.success i,
1781 .merge-icon.success i {
1781 .merge-icon.success i {
1782 color:@alert1;
1782 color:@alert1;
1783 }
1783 }
1784
1784
1785 .merge-message.warning i,
1785 .merge-message.warning i,
1786 .merge-icon.warning i {
1786 .merge-icon.warning i {
1787 color: @alert3;
1787 color: @alert3;
1788 }
1788 }
1789
1789
1790 .merge-message.error i,
1790 .merge-message.error i,
1791 .merge-icon.error i {
1791 .merge-icon.error i {
1792 color:@alert2;
1792 color:@alert2;
1793 }
1793 }
1794
1794
1795 .pr-versions {
1795 .pr-versions {
1796 font-size: 1.1em;
1796 font-size: 1.1em;
1797
1797
1798 table {
1798 table {
1799 padding: 0px 5px;
1799 padding: 0px 5px;
1800 }
1800 }
1801
1801
1802 td {
1802 td {
1803 line-height: 15px;
1803 line-height: 15px;
1804 }
1804 }
1805
1805
1806 .flag_status {
1806 .flag_status {
1807 margin: 0;
1807 margin: 0;
1808 }
1808 }
1809
1809
1810 .compare-radio-button {
1810 .compare-radio-button {
1811 position: relative;
1811 position: relative;
1812 top: -3px;
1812 top: -3px;
1813 }
1813 }
1814 }
1814 }
1815
1815
1816
1816
1817 #close_pull_request {
1817 #close_pull_request {
1818 margin-right: 0px;
1818 margin-right: 0px;
1819 }
1819 }
1820
1820
1821 .empty_data {
1821 .empty_data {
1822 color: @grey4;
1822 color: @grey4;
1823 }
1823 }
1824
1824
1825 #changeset_compare_view_content {
1825 #changeset_compare_view_content {
1826 clear: both;
1826 clear: both;
1827 width: 100%;
1827 width: 100%;
1828 box-sizing: border-box;
1828 box-sizing: border-box;
1829 .border-radius(@border-radius);
1829 .border-radius(@border-radius);
1830
1830
1831 .help-block {
1831 .help-block {
1832 margin: @padding 0;
1832 margin: @padding 0;
1833 color: @text-color;
1833 color: @text-color;
1834 &.pre-formatting {
1834 &.pre-formatting {
1835 white-space: pre;
1835 white-space: pre;
1836 }
1836 }
1837 }
1837 }
1838
1838
1839 .empty_data {
1839 .empty_data {
1840 margin: @padding 0;
1840 margin: @padding 0;
1841 }
1841 }
1842
1842
1843 .alert {
1843 .alert {
1844 margin-bottom: @space;
1844 margin-bottom: @space;
1845 }
1845 }
1846 }
1846 }
1847
1847
1848 .table_disp {
1848 .table_disp {
1849 .status {
1849 .status {
1850 width: auto;
1850 width: auto;
1851
1851
1852 .flag_status {
1852 .flag_status {
1853 float: left;
1853 float: left;
1854 }
1854 }
1855 }
1855 }
1856 }
1856 }
1857
1857
1858
1858
1859 .creation_in_progress {
1859 .creation_in_progress {
1860 color: @grey4
1860 color: @grey4
1861 }
1861 }
1862
1862
1863 .status_box_menu {
1863 .status_box_menu {
1864 margin: 0;
1864 margin: 0;
1865 }
1865 }
1866
1866
1867 .notification-table{
1867 .notification-table{
1868 margin-bottom: @space;
1868 margin-bottom: @space;
1869 display: table;
1869 display: table;
1870 width: 100%;
1870 width: 100%;
1871
1871
1872 .container{
1872 .container{
1873 display: table-row;
1873 display: table-row;
1874
1874
1875 .notification-header{
1875 .notification-header{
1876 border-bottom: @border-thickness solid @border-default-color;
1876 border-bottom: @border-thickness solid @border-default-color;
1877 }
1877 }
1878
1878
1879 .notification-subject{
1879 .notification-subject{
1880 display: table-cell;
1880 display: table-cell;
1881 }
1881 }
1882 }
1882 }
1883 }
1883 }
1884
1884
1885 // Notifications
1885 // Notifications
1886 .notification-header{
1886 .notification-header{
1887 display: table;
1887 display: table;
1888 width: 100%;
1888 width: 100%;
1889 padding: floor(@basefontsize/2) 0;
1889 padding: floor(@basefontsize/2) 0;
1890 line-height: 1em;
1890 line-height: 1em;
1891
1891
1892 .desc, .delete-notifications, .read-notifications{
1892 .desc, .delete-notifications, .read-notifications{
1893 display: table-cell;
1893 display: table-cell;
1894 text-align: left;
1894 text-align: left;
1895 }
1895 }
1896
1896
1897 .desc{
1897 .desc{
1898 width: 1163px;
1898 width: 1163px;
1899 }
1899 }
1900
1900
1901 .delete-notifications, .read-notifications{
1901 .delete-notifications, .read-notifications{
1902 width: 35px;
1902 width: 35px;
1903 min-width: 35px; //fixes when only one button is displayed
1903 min-width: 35px; //fixes when only one button is displayed
1904 }
1904 }
1905 }
1905 }
1906
1906
1907 .notification-body {
1907 .notification-body {
1908 .markdown-block,
1908 .markdown-block,
1909 .rst-block {
1909 .rst-block {
1910 padding: @padding 0;
1910 padding: @padding 0;
1911 }
1911 }
1912
1912
1913 .notification-subject {
1913 .notification-subject {
1914 padding: @textmargin 0;
1914 padding: @textmargin 0;
1915 border-bottom: @border-thickness solid @border-default-color;
1915 border-bottom: @border-thickness solid @border-default-color;
1916 }
1916 }
1917 }
1917 }
1918
1918
1919
1919
1920 .notifications_buttons{
1920 .notifications_buttons{
1921 float: right;
1921 float: right;
1922 }
1922 }
1923
1923
1924 #notification-status{
1924 #notification-status{
1925 display: inline;
1925 display: inline;
1926 }
1926 }
1927
1927
1928 // Repositories
1928 // Repositories
1929
1929
1930 #summary.fields{
1930 #summary.fields{
1931 display: table;
1931 display: table;
1932
1932
1933 .field{
1933 .field{
1934 display: table-row;
1934 display: table-row;
1935
1935
1936 .label-summary{
1936 .label-summary{
1937 display: table-cell;
1937 display: table-cell;
1938 min-width: @label-summary-minwidth;
1938 min-width: @label-summary-minwidth;
1939 padding-top: @padding/2;
1939 padding-top: @padding/2;
1940 padding-bottom: @padding/2;
1940 padding-bottom: @padding/2;
1941 padding-right: @padding/2;
1941 padding-right: @padding/2;
1942 }
1942 }
1943
1943
1944 .input{
1944 .input{
1945 display: table-cell;
1945 display: table-cell;
1946 padding: @padding/2;
1946 padding: @padding/2;
1947
1947
1948 input{
1948 input{
1949 min-width: 29em;
1949 min-width: 29em;
1950 padding: @padding/4;
1950 padding: @padding/4;
1951 }
1951 }
1952 }
1952 }
1953 .statistics, .downloads{
1953 .statistics, .downloads{
1954 .disabled{
1954 .disabled{
1955 color: @grey4;
1955 color: @grey4;
1956 }
1956 }
1957 }
1957 }
1958 }
1958 }
1959 }
1959 }
1960
1960
1961 #summary{
1961 #summary{
1962 width: 70%;
1962 width: 70%;
1963 }
1963 }
1964
1964
1965
1965
1966 // Journal
1966 // Journal
1967 .journal.title {
1967 .journal.title {
1968 h5 {
1968 h5 {
1969 float: left;
1969 float: left;
1970 margin: 0;
1970 margin: 0;
1971 width: 70%;
1971 width: 70%;
1972 }
1972 }
1973
1973
1974 ul {
1974 ul {
1975 float: right;
1975 float: right;
1976 display: inline-block;
1976 display: inline-block;
1977 margin: 0;
1977 margin: 0;
1978 width: 30%;
1978 width: 30%;
1979 text-align: right;
1979 text-align: right;
1980
1980
1981 li {
1981 li {
1982 display: inline;
1982 display: inline;
1983 font-size: @journal-fontsize;
1983 font-size: @journal-fontsize;
1984 line-height: 1em;
1984 line-height: 1em;
1985
1985
1986 list-style-type: none;
1986 list-style-type: none;
1987 }
1987 }
1988 }
1988 }
1989 }
1989 }
1990
1990
1991 .filterexample {
1991 .filterexample {
1992 position: absolute;
1992 position: absolute;
1993 top: 95px;
1993 top: 95px;
1994 left: @contentpadding;
1994 left: @contentpadding;
1995 color: @rcblue;
1995 color: @rcblue;
1996 font-size: 11px;
1996 font-size: 11px;
1997 font-family: @text-regular;
1997 font-family: @text-regular;
1998 cursor: help;
1998 cursor: help;
1999
1999
2000 &:hover {
2000 &:hover {
2001 color: @rcdarkblue;
2001 color: @rcdarkblue;
2002 }
2002 }
2003
2003
2004 @media (max-width:768px) {
2004 @media (max-width:768px) {
2005 position: relative;
2005 position: relative;
2006 top: auto;
2006 top: auto;
2007 left: auto;
2007 left: auto;
2008 display: block;
2008 display: block;
2009 }
2009 }
2010 }
2010 }
2011
2011
2012
2012
2013 #journal{
2013 #journal{
2014 margin-bottom: @space;
2014 margin-bottom: @space;
2015
2015
2016 .journal_day{
2016 .journal_day{
2017 margin-bottom: @textmargin/2;
2017 margin-bottom: @textmargin/2;
2018 padding-bottom: @textmargin/2;
2018 padding-bottom: @textmargin/2;
2019 font-size: @journal-fontsize;
2019 font-size: @journal-fontsize;
2020 border-bottom: @border-thickness solid @border-default-color;
2020 border-bottom: @border-thickness solid @border-default-color;
2021 }
2021 }
2022
2022
2023 .journal_container{
2023 .journal_container{
2024 margin-bottom: @space;
2024 margin-bottom: @space;
2025
2025
2026 .journal_user{
2026 .journal_user{
2027 display: inline-block;
2027 display: inline-block;
2028 }
2028 }
2029 .journal_action_container{
2029 .journal_action_container{
2030 display: block;
2030 display: block;
2031 margin-top: @textmargin;
2031 margin-top: @textmargin;
2032
2032
2033 div{
2033 div{
2034 display: inline;
2034 display: inline;
2035 }
2035 }
2036
2036
2037 div.journal_action_params{
2037 div.journal_action_params{
2038 display: block;
2038 display: block;
2039 }
2039 }
2040
2040
2041 div.journal_repo:after{
2041 div.journal_repo:after{
2042 content: "\A";
2042 content: "\A";
2043 white-space: pre;
2043 white-space: pre;
2044 }
2044 }
2045
2045
2046 div.date{
2046 div.date{
2047 display: block;
2047 display: block;
2048 margin-bottom: @textmargin;
2048 margin-bottom: @textmargin;
2049 }
2049 }
2050 }
2050 }
2051 }
2051 }
2052 }
2052 }
2053
2053
2054 // Files
2054 // Files
2055 .edit-file-title {
2055 .edit-file-title {
2056 border-bottom: @border-thickness solid @border-default-color;
2056 border-bottom: @border-thickness solid @border-default-color;
2057
2057
2058 .breadcrumbs {
2058 .breadcrumbs {
2059 margin-bottom: 0;
2059 margin-bottom: 0;
2060 }
2060 }
2061 }
2061 }
2062
2062
2063 .edit-file-fieldset {
2063 .edit-file-fieldset {
2064 margin-top: @sidebarpadding;
2064 margin-top: @sidebarpadding;
2065
2065
2066 .fieldset {
2066 .fieldset {
2067 .left-label {
2067 .left-label {
2068 width: 13%;
2068 width: 13%;
2069 }
2069 }
2070 .right-content {
2070 .right-content {
2071 width: 87%;
2071 width: 87%;
2072 max-width: 100%;
2072 max-width: 100%;
2073 }
2073 }
2074 .filename-label {
2074 .filename-label {
2075 margin-top: 13px;
2075 margin-top: 13px;
2076 }
2076 }
2077 .commit-message-label {
2077 .commit-message-label {
2078 margin-top: 4px;
2078 margin-top: 4px;
2079 }
2079 }
2080 .file-upload-input {
2080 .file-upload-input {
2081 input {
2081 input {
2082 display: none;
2082 display: none;
2083 }
2083 }
2084 margin-top: 10px;
2084 margin-top: 10px;
2085 }
2085 }
2086 .file-upload-label {
2086 .file-upload-label {
2087 margin-top: 10px;
2087 margin-top: 10px;
2088 }
2088 }
2089 p {
2089 p {
2090 margin-top: 5px;
2090 margin-top: 5px;
2091 }
2091 }
2092
2092
2093 }
2093 }
2094 .custom-path-link {
2094 .custom-path-link {
2095 margin-left: 5px;
2095 margin-left: 5px;
2096 }
2096 }
2097 #commit {
2097 #commit {
2098 resize: vertical;
2098 resize: vertical;
2099 }
2099 }
2100 }
2100 }
2101
2101
2102 .delete-file-preview {
2102 .delete-file-preview {
2103 max-height: 250px;
2103 max-height: 250px;
2104 }
2104 }
2105
2105
2106 .new-file,
2106 .new-file,
2107 #filter_activate,
2107 #filter_activate,
2108 #filter_deactivate {
2108 #filter_deactivate {
2109 float: right;
2109 float: right;
2110 margin: 0 0 0 10px;
2110 margin: 0 0 0 10px;
2111 }
2111 }
2112
2112
2113 h3.files_location{
2113 h3.files_location{
2114 line-height: 2.4em;
2114 line-height: 2.4em;
2115 }
2115 }
2116
2116
2117 .browser-nav {
2117 .browser-nav {
2118 width: 100%;
2118 width: 100%;
2119 display: table;
2119 display: table;
2120 margin-bottom: 20px;
2120 margin-bottom: 20px;
2121
2121
2122 .info_box {
2122 .info_box {
2123 float: left;
2123 float: left;
2124 display: inline-table;
2124 display: inline-table;
2125 height: 2.5em;
2125 height: 2.5em;
2126
2126
2127 .browser-cur-rev, .info_box_elem {
2127 .browser-cur-rev, .info_box_elem {
2128 display: table-cell;
2128 display: table-cell;
2129 vertical-align: middle;
2129 vertical-align: middle;
2130 }
2130 }
2131
2131
2132 .drop-menu {
2132 .drop-menu {
2133 margin: 0 10px;
2133 margin: 0 10px;
2134 }
2134 }
2135
2135
2136 .info_box_elem {
2136 .info_box_elem {
2137 border-top: @border-thickness solid @grey5;
2137 border-top: @border-thickness solid @grey5;
2138 border-bottom: @border-thickness solid @grey5;
2138 border-bottom: @border-thickness solid @grey5;
2139 box-shadow: @button-shadow;
2139 box-shadow: @button-shadow;
2140
2140
2141 #at_rev, a {
2141 #at_rev, a {
2142 padding: 0.6em 0.4em;
2142 padding: 0.6em 0.4em;
2143 margin: 0;
2143 margin: 0;
2144 .box-shadow(none);
2144 .box-shadow(none);
2145 border: 0;
2145 border: 0;
2146 height: 12px;
2146 height: 12px;
2147 color: @grey2;
2147 color: @grey2;
2148 }
2148 }
2149
2149
2150 input#at_rev {
2150 input#at_rev {
2151 max-width: 50px;
2151 max-width: 50px;
2152 text-align: center;
2152 text-align: center;
2153 }
2153 }
2154
2154
2155 &.previous {
2155 &.previous {
2156 border: @border-thickness solid @grey5;
2156 border: @border-thickness solid @grey5;
2157 border-top-left-radius: @border-radius;
2157 border-top-left-radius: @border-radius;
2158 border-bottom-left-radius: @border-radius;
2158 border-bottom-left-radius: @border-radius;
2159
2159
2160 &:hover {
2160 &:hover {
2161 border-color: @grey4;
2161 border-color: @grey4;
2162 }
2162 }
2163
2163
2164 .disabled {
2164 .disabled {
2165 color: @grey5;
2165 color: @grey5;
2166 cursor: not-allowed;
2166 cursor: not-allowed;
2167 opacity: 0.5;
2167 opacity: 0.5;
2168 }
2168 }
2169 }
2169 }
2170
2170
2171 &.next {
2171 &.next {
2172 border: @border-thickness solid @grey5;
2172 border: @border-thickness solid @grey5;
2173 border-top-right-radius: @border-radius;
2173 border-top-right-radius: @border-radius;
2174 border-bottom-right-radius: @border-radius;
2174 border-bottom-right-radius: @border-radius;
2175
2175
2176 &:hover {
2176 &:hover {
2177 border-color: @grey4;
2177 border-color: @grey4;
2178 }
2178 }
2179
2179
2180 .disabled {
2180 .disabled {
2181 color: @grey5;
2181 color: @grey5;
2182 cursor: not-allowed;
2182 cursor: not-allowed;
2183 opacity: 0.5;
2183 opacity: 0.5;
2184 }
2184 }
2185 }
2185 }
2186 }
2186 }
2187
2187
2188 .browser-cur-rev {
2188 .browser-cur-rev {
2189
2189
2190 span{
2190 span{
2191 margin: 0;
2191 margin: 0;
2192 color: @rcblue;
2192 color: @rcblue;
2193 height: 12px;
2193 height: 12px;
2194 display: inline-block;
2194 display: inline-block;
2195 padding: 0.7em 1em ;
2195 padding: 0.7em 1em ;
2196 border: @border-thickness solid @rcblue;
2196 border: @border-thickness solid @rcblue;
2197 margin-right: @padding;
2197 margin-right: @padding;
2198 }
2198 }
2199 }
2199 }
2200
2200
2201 }
2201 }
2202
2202
2203 .select-index-number {
2203 .select-index-number {
2204 margin: 0 0 0 20px;
2204 margin: 0 0 0 20px;
2205 color: @grey3;
2205 color: @grey3;
2206 }
2206 }
2207
2207
2208 .search_activate {
2208 .search_activate {
2209 display: table-cell;
2209 display: table-cell;
2210 vertical-align: middle;
2210 vertical-align: middle;
2211
2211
2212 input, label{
2212 input, label{
2213 margin: 0;
2213 margin: 0;
2214 padding: 0;
2214 padding: 0;
2215 }
2215 }
2216
2216
2217 input{
2217 input{
2218 margin-left: @textmargin;
2218 margin-left: @textmargin;
2219 }
2219 }
2220
2220
2221 }
2221 }
2222 }
2222 }
2223
2223
2224 .browser-cur-rev{
2224 .browser-cur-rev{
2225 margin-bottom: @textmargin;
2225 margin-bottom: @textmargin;
2226 }
2226 }
2227
2227
2228 #node_filter_box_loading{
2228 #node_filter_box_loading{
2229 .info_text;
2229 .info_text;
2230 }
2230 }
2231
2231
2232 .browser-search {
2232 .browser-search {
2233 margin: -25px 0px 5px 0px;
2233 margin: -25px 0px 5px 0px;
2234 }
2234 }
2235
2235
2236 .files-quick-filter {
2236 .files-quick-filter {
2237 float: right;
2237 float: right;
2238 width: 180px;
2238 width: 180px;
2239 position: relative;
2239 position: relative;
2240 }
2240 }
2241
2241
2242 .files-filter-box {
2242 .files-filter-box {
2243 display: flex;
2243 display: flex;
2244 padding: 0px;
2244 padding: 0px;
2245 border-radius: 3px;
2245 border-radius: 3px;
2246 margin-bottom: 0;
2246 margin-bottom: 0;
2247
2247
2248 a {
2248 a {
2249 border: none !important;
2249 border: none !important;
2250 }
2250 }
2251
2251
2252 li {
2252 li {
2253 list-style-type: none
2253 list-style-type: none
2254 }
2254 }
2255 }
2255 }
2256
2256
2257 .files-filter-box-path {
2257 .files-filter-box-path {
2258 line-height: 33px;
2258 line-height: 33px;
2259 padding: 0;
2259 padding: 0;
2260 width: 20px;
2260 width: 20px;
2261 position: absolute;
2261 position: absolute;
2262 z-index: 11;
2262 z-index: 11;
2263 left: 5px;
2263 left: 5px;
2264 }
2264 }
2265
2265
2266 .files-filter-box-input {
2266 .files-filter-box-input {
2267 margin-right: 0;
2267 margin-right: 0;
2268
2268
2269 input {
2269 input {
2270 border: 1px solid @white;
2270 border: 1px solid @white;
2271 padding-left: 25px;
2271 padding-left: 25px;
2272 width: 145px;
2272 width: 145px;
2273
2273
2274 &:hover {
2274 &:hover {
2275 border-color: @grey6;
2275 border-color: @grey6;
2276 }
2276 }
2277
2277
2278 &:focus {
2278 &:focus {
2279 border-color: @grey5;
2279 border-color: @grey5;
2280 }
2280 }
2281 }
2281 }
2282 }
2282 }
2283
2283
2284 .browser-result{
2284 .browser-result{
2285 td a{
2285 td a{
2286 margin-left: 0.5em;
2286 margin-left: 0.5em;
2287 display: inline-block;
2287 display: inline-block;
2288
2288
2289 em {
2289 em {
2290 font-weight: @text-bold-weight;
2290 font-weight: @text-bold-weight;
2291 font-family: @text-bold;
2291 font-family: @text-bold;
2292 }
2292 }
2293 }
2293 }
2294 }
2294 }
2295
2295
2296 .browser-highlight{
2296 .browser-highlight{
2297 background-color: @grey5-alpha;
2297 background-color: @grey5-alpha;
2298 }
2298 }
2299
2299
2300
2300
2301 // Search
2301 // Search
2302
2302
2303 .search-form{
2303 .search-form{
2304 #q {
2304 #q {
2305 width: @search-form-width;
2305 width: @search-form-width;
2306 }
2306 }
2307 .fields{
2307 .fields{
2308 margin: 0 0 @space;
2308 margin: 0 0 @space;
2309 }
2309 }
2310
2310
2311 label{
2311 label{
2312 display: inline-block;
2312 display: inline-block;
2313 margin-right: @textmargin;
2313 margin-right: @textmargin;
2314 padding-top: 0.25em;
2314 padding-top: 0.25em;
2315 }
2315 }
2316
2316
2317
2317
2318 .results{
2318 .results{
2319 clear: both;
2319 clear: both;
2320 margin: 0 0 @padding;
2320 margin: 0 0 @padding;
2321 }
2321 }
2322
2322
2323 .search-tags {
2323 .search-tags {
2324 padding: 5px 0;
2324 padding: 5px 0;
2325 }
2325 }
2326 }
2326 }
2327
2327
2328 div.search-feedback-items {
2328 div.search-feedback-items {
2329 display: inline-block;
2329 display: inline-block;
2330 }
2330 }
2331
2331
2332 div.search-code-body {
2332 div.search-code-body {
2333 background-color: #ffffff; padding: 5px 0 5px 10px;
2333 background-color: #ffffff; padding: 5px 0 5px 10px;
2334 pre {
2334 pre {
2335 .match { background-color: #faffa6;}
2335 .match { background-color: #faffa6;}
2336 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2336 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
2337 }
2337 }
2338 }
2338 }
2339
2339
2340 .expand_commit.search {
2340 .expand_commit.search {
2341 .show_more.open {
2341 .show_more.open {
2342 height: auto;
2342 height: auto;
2343 max-height: none;
2343 max-height: none;
2344 }
2344 }
2345 }
2345 }
2346
2346
2347 .search-results {
2347 .search-results {
2348
2348
2349 h2 {
2349 h2 {
2350 margin-bottom: 0;
2350 margin-bottom: 0;
2351 }
2351 }
2352 .codeblock {
2352 .codeblock {
2353 border: none;
2353 border: none;
2354 background: transparent;
2354 background: transparent;
2355 }
2355 }
2356
2356
2357 .codeblock-header {
2357 .codeblock-header {
2358 border: none;
2358 border: none;
2359 background: transparent;
2359 background: transparent;
2360 }
2360 }
2361
2361
2362 .code-body {
2362 .code-body {
2363 border: @border-thickness solid @border-default-color;
2363 border: @border-thickness solid @grey6;
2364 .border-radius(@border-radius);
2364 .border-radius(@border-radius);
2365 }
2365 }
2366
2366
2367 .td-commit {
2367 .td-commit {
2368 &:extend(pre);
2368 &:extend(pre);
2369 border-bottom: @border-thickness solid @border-default-color;
2369 border-bottom: @border-thickness solid @border-default-color;
2370 }
2370 }
2371
2371
2372 .message {
2372 .message {
2373 height: auto;
2373 height: auto;
2374 max-width: 350px;
2374 max-width: 350px;
2375 white-space: normal;
2375 white-space: normal;
2376 text-overflow: initial;
2376 text-overflow: initial;
2377 overflow: visible;
2377 overflow: visible;
2378
2378
2379 .match { background-color: #faffa6;}
2379 .match { background-color: #faffa6;}
2380 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2380 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2381 }
2381 }
2382
2382
2383 .path {
2384 border-bottom: none !important;
2385 border-left: 1px solid @grey6 !important;
2386 border-right: 1px solid @grey6 !important;
2387 }
2383 }
2388 }
2384
2389
2385 table.rctable td.td-search-results div {
2390 table.rctable td.td-search-results div {
2386 max-width: 100%;
2391 max-width: 100%;
2387 }
2392 }
2388
2393
2389 #tip-box, .tip-box{
2394 #tip-box, .tip-box{
2390 padding: @menupadding/2;
2395 padding: @menupadding/2;
2391 display: block;
2396 display: block;
2392 border: @border-thickness solid @border-highlight-color;
2397 border: @border-thickness solid @border-highlight-color;
2393 .border-radius(@border-radius);
2398 .border-radius(@border-radius);
2394 background-color: white;
2399 background-color: white;
2395 z-index: 99;
2400 z-index: 99;
2396 white-space: pre-wrap;
2401 white-space: pre-wrap;
2397 }
2402 }
2398
2403
2399 #linktt {
2404 #linktt {
2400 width: 79px;
2405 width: 79px;
2401 }
2406 }
2402
2407
2403 #help_kb .modal-content{
2408 #help_kb .modal-content{
2404 max-width: 750px;
2409 max-width: 750px;
2405 margin: 10% auto;
2410 margin: 10% auto;
2406
2411
2407 table{
2412 table{
2408 td,th{
2413 td,th{
2409 border-bottom: none;
2414 border-bottom: none;
2410 line-height: 2.5em;
2415 line-height: 2.5em;
2411 }
2416 }
2412 th{
2417 th{
2413 padding-bottom: @textmargin/2;
2418 padding-bottom: @textmargin/2;
2414 }
2419 }
2415 td.keys{
2420 td.keys{
2416 text-align: center;
2421 text-align: center;
2417 }
2422 }
2418 }
2423 }
2419
2424
2420 .block-left{
2425 .block-left{
2421 width: 45%;
2426 width: 45%;
2422 margin-right: 5%;
2427 margin-right: 5%;
2423 }
2428 }
2424 .modal-footer{
2429 .modal-footer{
2425 clear: both;
2430 clear: both;
2426 }
2431 }
2427 .key.tag{
2432 .key.tag{
2428 padding: 0.5em;
2433 padding: 0.5em;
2429 background-color: @rcblue;
2434 background-color: @rcblue;
2430 color: white;
2435 color: white;
2431 border-color: @rcblue;
2436 border-color: @rcblue;
2432 .box-shadow(none);
2437 .box-shadow(none);
2433 }
2438 }
2434 }
2439 }
2435
2440
2436
2441
2437
2442
2438 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2443 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2439
2444
2440 @import 'statistics-graph';
2445 @import 'statistics-graph';
2441 @import 'tables';
2446 @import 'tables';
2442 @import 'forms';
2447 @import 'forms';
2443 @import 'diff';
2448 @import 'diff';
2444 @import 'summary';
2449 @import 'summary';
2445 @import 'navigation';
2450 @import 'navigation';
2446
2451
2447 //--- SHOW/HIDE SECTIONS --//
2452 //--- SHOW/HIDE SECTIONS --//
2448
2453
2449 .btn-collapse {
2454 .btn-collapse {
2450 float: right;
2455 float: right;
2451 text-align: right;
2456 text-align: right;
2452 font-family: @text-light;
2457 font-family: @text-light;
2453 font-size: @basefontsize;
2458 font-size: @basefontsize;
2454 cursor: pointer;
2459 cursor: pointer;
2455 border: none;
2460 border: none;
2456 color: @rcblue;
2461 color: @rcblue;
2457 }
2462 }
2458
2463
2459 table.rctable,
2464 table.rctable,
2460 table.dataTable {
2465 table.dataTable {
2461 .btn-collapse {
2466 .btn-collapse {
2462 float: right;
2467 float: right;
2463 text-align: right;
2468 text-align: right;
2464 }
2469 }
2465 }
2470 }
2466
2471
2467 table.rctable {
2472 table.rctable {
2468 &.permissions {
2473 &.permissions {
2469
2474
2470 th.td-owner {
2475 th.td-owner {
2471 padding: 0;
2476 padding: 0;
2472 }
2477 }
2473
2478
2474 th {
2479 th {
2475 font-weight: normal;
2480 font-weight: normal;
2476 padding: 0 5px;
2481 padding: 0 5px;
2477 }
2482 }
2478
2483
2479 }
2484 }
2480 }
2485 }
2481
2486
2482
2487
2483 // TODO: johbo: Fix for IE10, this avoids that we see a border
2488 // TODO: johbo: Fix for IE10, this avoids that we see a border
2484 // and padding around checkboxes and radio boxes. Move to the right place,
2489 // and padding around checkboxes and radio boxes. Move to the right place,
2485 // or better: Remove this once we did the form refactoring.
2490 // or better: Remove this once we did the form refactoring.
2486 input[type=checkbox],
2491 input[type=checkbox],
2487 input[type=radio] {
2492 input[type=radio] {
2488 padding: 0;
2493 padding: 0;
2489 border: none;
2494 border: none;
2490 }
2495 }
2491
2496
2492 .toggle-ajax-spinner{
2497 .toggle-ajax-spinner{
2493 height: 16px;
2498 height: 16px;
2494 width: 16px;
2499 width: 16px;
2495 }
2500 }
2496
2501
2497
2502
2498 .markup-form .clearfix {
2503 .markup-form .clearfix {
2499 .border-radius(@border-radius);
2504 .border-radius(@border-radius);
2500 margin: 0px;
2505 margin: 0px;
2501 }
2506 }
2502
2507
2503 .markup-form-area {
2508 .markup-form-area {
2504 padding: 8px 12px;
2509 padding: 8px 12px;
2505 border: 1px solid @grey4;
2510 border: 1px solid @grey4;
2506 .border-radius(@border-radius);
2511 .border-radius(@border-radius);
2507 }
2512 }
2508
2513
2509 .markup-form-area-header .nav-links {
2514 .markup-form-area-header .nav-links {
2510 display: flex;
2515 display: flex;
2511 flex-flow: row wrap;
2516 flex-flow: row wrap;
2512 -webkit-flex-flow: row wrap;
2517 -webkit-flex-flow: row wrap;
2513 width: 100%;
2518 width: 100%;
2514 }
2519 }
2515
2520
2516 .markup-form-area-footer {
2521 .markup-form-area-footer {
2517 display: flex;
2522 display: flex;
2518 }
2523 }
2519
2524
2520 .markup-form-area-footer .toolbar {
2525 .markup-form-area-footer .toolbar {
2521
2526
2522 }
2527 }
2523
2528
2524 // markup Form
2529 // markup Form
2525 div.markup-form {
2530 div.markup-form {
2526 margin-top: 20px;
2531 margin-top: 20px;
2527 }
2532 }
2528
2533
2529 .markup-form strong {
2534 .markup-form strong {
2530 display: block;
2535 display: block;
2531 margin-bottom: 15px;
2536 margin-bottom: 15px;
2532 }
2537 }
2533
2538
2534 .markup-form textarea {
2539 .markup-form textarea {
2535 width: 100%;
2540 width: 100%;
2536 height: 100px;
2541 height: 100px;
2537 font-family: @text-monospace;
2542 font-family: @text-monospace;
2538 }
2543 }
2539
2544
2540 form.markup-form {
2545 form.markup-form {
2541 margin-top: 10px;
2546 margin-top: 10px;
2542 margin-left: 10px;
2547 margin-left: 10px;
2543 }
2548 }
2544
2549
2545 .markup-form .comment-block-ta,
2550 .markup-form .comment-block-ta,
2546 .markup-form .preview-box {
2551 .markup-form .preview-box {
2547 .border-radius(@border-radius);
2552 .border-radius(@border-radius);
2548 .box-sizing(border-box);
2553 .box-sizing(border-box);
2549 background-color: white;
2554 background-color: white;
2550 }
2555 }
2551
2556
2552 .markup-form .preview-box.unloaded {
2557 .markup-form .preview-box.unloaded {
2553 height: 50px;
2558 height: 50px;
2554 text-align: center;
2559 text-align: center;
2555 padding: 20px;
2560 padding: 20px;
2556 background-color: white;
2561 background-color: white;
2557 }
2562 }
2558
2563
2559
2564
2560 .dropzone-wrapper {
2565 .dropzone-wrapper {
2561 border: 1px solid @grey5;
2566 border: 1px solid @grey5;
2562 padding: 20px;
2567 padding: 20px;
2563 }
2568 }
2564
2569
2565 .dropzone {
2570 .dropzone {
2566 border: 2px dashed @grey5;
2571 border: 2px dashed @grey5;
2567 border-radius: 5px;
2572 border-radius: 5px;
2568 background: white;
2573 background: white;
2569 min-height: 200px;
2574 min-height: 200px;
2570 padding: 54px;
2575 padding: 54px;
2571 }
2576 }
2572 .dropzone .dz-message {
2577 .dropzone .dz-message {
2573 font-weight: 700;
2578 font-weight: 700;
2574 }
2579 }
2575
2580
2576 .dropzone .dz-message {
2581 .dropzone .dz-message {
2577 text-align: center;
2582 text-align: center;
2578 margin: 2em 0;
2583 margin: 2em 0;
2579 }
2584 }
2580
2585
2581 .dz-preview {
2586 .dz-preview {
2582 margin: 10px 0px !important;
2587 margin: 10px 0px !important;
2583 position: relative;
2588 position: relative;
2584 vertical-align: top;
2589 vertical-align: top;
2585 padding: 10px;
2590 padding: 10px;
2586 }
2591 }
2587
2592
2588 .dz-filename {
2593 .dz-filename {
2589 font-weight: 700;
2594 font-weight: 700;
2590 float:left;
2595 float:left;
2591 }
2596 }
2592
2597
2593 .dz-response {
2598 .dz-response {
2594 clear:both
2599 clear:both
2595 }
2600 }
2596
2601
2597 .dz-filename-size {
2602 .dz-filename-size {
2598 float:right
2603 float:right
2599 }
2604 }
2600
2605
2601 .dz-error-message {
2606 .dz-error-message {
2602 color: @alert2;
2607 color: @alert2;
2603 } No newline at end of file
2608 }
@@ -1,180 +1,165 b''
1 <%namespace name="search" file="/search/search.mako"/>
1 <%namespace name="search" file="/search/search.mako"/>
2
2
3 <%def name="highlight_text_file(has_matched_content, file_content, lexer, html_formatter, matching_lines, shown_matching_lines, url, use_hl_filter)">
3 <%def name="highlight_text_file(has_matched_content, file_content, lexer, html_formatter, matching_lines, shown_matching_lines, url, use_hl_filter)">
4 % if has_matched_content:
4 % if has_matched_content:
5 ${h.code_highlight(file_content, lexer, html_formatter, use_hl_filter=use_hl_filter)|n}
5 ${h.code_highlight(file_content, lexer, html_formatter, use_hl_filter=use_hl_filter)|n}
6 % else:
6 % else:
7 ${_('No content matched')} <br/>
7 ${_('No content matched')} <br/>
8 % endif
8 % endif
9
9
10 %if len(matching_lines) > shown_matching_lines:
10 %if len(matching_lines) > shown_matching_lines:
11 <a href="${url}">
11 <a href="${url}">
12 ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')}
12 ${len(matching_lines) - shown_matching_lines} ${_('more matches in this file')}
13 </a>
13 </a>
14 %endif
14 %endif
15 </%def>
15 </%def>
16
16
17 <div class="search-results">
17 <div class="search-results">
18 <% query_mark = c.searcher.query_to_mark(c.cur_query, 'content') %>
18 <% query_mark = c.searcher.query_to_mark(c.cur_query, 'content') %>
19
19
20 %for entry in c.formatted_results:
20 %for entry in c.formatted_results:
21
21
22 <%
22 <%
23 file_content = entry['content_highlight'] or entry['content']
23 file_content = entry['content_highlight'] or entry['content']
24 mimetype = entry.get('mimetype')
24 mimetype = entry.get('mimetype')
25 filepath = entry.get('path')
25 filepath = entry.get('path')
26 max_lines = h.safe_int(request.GET.get('max_lines', '10'))
26 max_lines = h.safe_int(request.GET.get('max_lines', '10'))
27 line_context = h.safe_int(request.GET.get('line_contenxt', '3'))
27 line_context = h.safe_int(request.GET.get('line_contenxt', '3'))
28
28
29 match_file_url=h.route_path('repo_files',repo_name=entry['repository'], commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path'], _query={"mark": query_mark})
29 match_file_url=h.route_path('repo_files',repo_name=entry['repository'], commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path'], _query={"mark": query_mark})
30 terms = c.cur_query
30 terms = c.cur_query
31
31
32 if c.searcher.is_es_6:
32 if c.searcher.is_es_6:
33 # use empty terms so we default to markers usage
33 # use empty terms so we default to markers usage
34 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms=None)
34 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms=None)
35 else:
35 else:
36 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms)
36 total_lines, matching_lines = h.get_matching_line_offsets(file_content, terms)
37
37
38 shown_matching_lines = 0
38 shown_matching_lines = 0
39 lines_of_interest = set()
39 lines_of_interest = set()
40 for line_number in matching_lines:
40 for line_number in matching_lines:
41 if len(lines_of_interest) < max_lines:
41 if len(lines_of_interest) < max_lines:
42 lines_of_interest |= set(range(
42 lines_of_interest |= set(range(
43 max(line_number - line_context, 0),
43 max(line_number - line_context, 0),
44 min(line_number + line_context, total_lines + 1)))
44 min(line_number + line_context, total_lines + 1)))
45 shown_matching_lines += 1
45 shown_matching_lines += 1
46 lexer = h.get_lexer_safe(mimetype=mimetype, filepath=filepath)
46 lexer = h.get_lexer_safe(mimetype=mimetype, filepath=filepath)
47
47
48 html_formatter = h.SearchContentCodeHtmlFormatter(
48 html_formatter = h.SearchContentCodeHtmlFormatter(
49 linenos=True,
49 linenos=True,
50 cssclass="code-highlight",
50 cssclass="code-highlight",
51 url=match_file_url,
51 url=match_file_url,
52 query_terms=terms,
52 query_terms=terms,
53 only_line_numbers=lines_of_interest
53 only_line_numbers=lines_of_interest
54 )
54 )
55
55
56 has_matched_content = len(lines_of_interest) >= 1
56 has_matched_content = len(lines_of_interest) >= 1
57
57
58 %>
58 %>
59 ## search results are additionally filtered, and this check is just a safe gate
59 ## search results are additionally filtered, and this check is just a safe gate
60 % if c.rhodecode_user.is_admin or h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results content check'):
60 % if c.rhodecode_user.is_admin or h.HasRepoPermissionAny('repository.write','repository.read','repository.admin')(entry['repository'], 'search results content check'):
61 <div id="codeblock" class="codeblock">
61 <div class="codeblock">
62 <div class="codeblock-header">
62 <h1>
63 <h1>
63 <% repo_type = entry.get('repo_type') or h.get_repo_type_by_name(entry.get('repository')) %>
64 <% repo_type = entry.get('repo_type') or h.get_repo_type_by_name(entry.get('repository')) %>
64 ${search.repo_icon(repo_type)}
65 ${search.repo_icon(repo_type)}
65 ${h.link_to(entry['repository'], h.route_path('repo_summary', repo_name=entry['repository']))}
66 ${h.link_to(entry['repository'], h.route_path('repo_summary',repo_name=entry['repository']))}
66 </h1>
67 </h1>
68 ## level 1
69 <div class="file-container">
70
67
71 <div class="pull-left">
68 <div class="codeblock-header">
72 <span class="stats-filename">
69
73 <strong>
70 <div class="file-filename">
74 <i class="icon-file-text"></i>
71 <i class="icon-file"></i> ${entry['f_path'].split('/')[-1]}
75 ${h.link_to(h.literal(entry['f_path']), h.route_path('repo_files',repo_name=entry['repository'],commit_id=entry.get('commit_id', 'tip'),f_path=entry['f_path']))}
76 </strong>
77 </span>
78 <span class="item last">
79 <i class="tooltip icon-clipboard clipboard-action" data-clipboard-text="${entry['f_path']}" title="${_('Copy the full path')}"></i>
80 </span>
81 </div>
72 </div>
82
73
83 <div class="pull-right">
74 <div class="file-stats">
84 <div class="buttons">
75 <div class="stats-info">
85 <a id="file_history_overview_full" href="${h.route_path('repo_commits_file',repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path',''))}">
76 <span class="stats-first-item">
86 ${_('Show Full History')}
77 ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
87 </a>
78 (${len(matching_lines)} ${_ungettext('matched', 'matched', len(matching_lines))})
88 | ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
79 </span>
89 | ${h.link_to(_('Raw'), h.route_path('repo_file_raw', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
80 <span>
90 | ${h.link_to(_('Download'), h.route_path('repo_file_download',repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
81 % if entry.get('size'):
91 </div>
82 | ${h.format_byte_size_binary(entry['size'])}
83 % endif
84 </span>
85 <span>
86 % if entry.get('mimetype'):
87 | ${entry.get('mimetype', "unknown mimetype")}
88 % endif
89 </span>
90 </div>
91 </div>
92 </div>
93
94 <div class="path clear-fix">
95 <div class="pull-left">
96 ${h.files_breadcrumbs(entry['repository'],entry.get('commit_id', 'tip'),entry['f_path'], linkify_last_item=True)}
92 </div>
97 </div>
93
98
94 </div>
99 <div class="pull-right stats">
95 ## level 2
100 ## <a id="file_history_overview_full" href="${h.route_path('repo_commits_file',repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path',''))}">
96 <div class="file-container">
101 ## ${_('Show Full History')}
97
102 ## </a>
98 <div class="pull-left">
103 ## | ${h.link_to(_('Annotation'), h.route_path('repo_files:annotated', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
99 <span class="stats-first-item">
104 ## | ${h.link_to(_('Raw'), h.route_path('repo_file_raw', repo_name=entry.get('repository',''),commit_id=entry.get('commit_id', 'tip'),f_path=entry.get('f_path','')))}
100 %if entry.get('lines'):
101 ${entry.get('lines', 0.)} ${_ungettext('line', 'lines', entry.get('lines', 0.))}
102 (${len(matching_lines)} ${_ungettext('matched', 'matched', len(matching_lines))})
103 %endif
104 </span>
105
106 <span>
107 %if entry.get('size'):
108 | ${h.format_byte_size_binary(entry['size'])}
109 %endif
110 </span>
111
112 <span>
113 %if entry.get('mimetype'):
114 | ${entry.get('mimetype', "unknown mimetype")}
115 %endif
116 </span>
117 </div>
118
119 <div class="pull-right">
120 <div class="search-tags">
105 <div class="search-tags">
121
106
122 <% repo_group = entry.get('repository_group')%>
107 <% repo_group = entry.get('repository_group')%>
123 ## hiden if in repo group view
108 ## hiden if in repo group view
124 % if repo_group and not c.repo_group_name:
109 % if repo_group and not c.repo_group_name:
125 <span class="tag tag8">
110 <span class="tag tag8">
126 ${search.repo_group_icon()}
111 ${search.repo_group_icon()}
127 <a href="${h.route_path('search_repo_group', repo_group_name=repo_group, _query={'q': c.cur_query})}">${_('Narrow to this repository group')}</a>
112 <a href="${h.route_path('search_repo_group', repo_group_name=repo_group, _query={'q': c.cur_query})}">${_('Narrow to this repository group')}</a>
128 </span>
113 </span>
129 % endif
114 % endif
130 ## hiden if in repo view
115 ## hiden if in repo view
131 % if not c.repo_name:
116 % if not c.repo_name:
132 <span class="tag tag8">
117 <span class="tag tag8">
133 ${search.repo_icon(repo_type)}
118 ${search.repo_icon(repo_type)}
134 <a href="${h.route_path('search_repo', repo_name=entry.get('repo_name'), _query={'q': c.cur_query})}">${_('Narrow to this repository')}</a>
119 <a href="${h.route_path('search_repo', repo_name=entry.get('repo_name'), _query={'q': c.cur_query})}">${_('Narrow to this repository')}</a>
135 </span>
120 </span>
136 % endif
121 % endif
137 </div>
122 </div>
138 </div>
139
140 </div>
141
123
124 </div>
125 <div class="clear-fix"></div>
142 </div>
126 </div>
143 <div class="code-body search-code-body">
127
144
128
129 <div class="code-body search-code-body clear-fix">
145 ${highlight_text_file(
130 ${highlight_text_file(
146 has_matched_content=has_matched_content,
131 has_matched_content=has_matched_content,
147 file_content=file_content,
132 file_content=file_content,
148 lexer=lexer,
133 lexer=lexer,
149 html_formatter=html_formatter,
134 html_formatter=html_formatter,
150 matching_lines=matching_lines,
135 matching_lines=matching_lines,
151 shown_matching_lines=shown_matching_lines,
136 shown_matching_lines=shown_matching_lines,
152 url=match_file_url,
137 url=match_file_url,
153 use_hl_filter=c.searcher.is_es_6
138 use_hl_filter=c.searcher.is_es_6
154 )}
139 )}
155 </div>
140 </div>
156
141
157 </div>
142 </div>
158 % endif
143 % endif
159 %endfor
144 %endfor
160 </div>
145 </div>
161 %if c.cur_query and c.formatted_results:
146 %if c.cur_query and c.formatted_results:
162 <div class="pagination-wh pagination-left" >
147 <div class="pagination-wh pagination-left" >
163 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
148 ${c.formatted_results.pager('$link_previous ~2~ $link_next')}
164 </div>
149 </div>
165 %endif
150 %endif
166
151
167 %if c.cur_query:
152 %if c.cur_query:
168 <script type="text/javascript">
153 <script type="text/javascript">
169 $(function(){
154 $(function(){
170 $(".search-code-body").mark(
155 $(".search-code-body").mark(
171 "${query_mark}",
156 "${query_mark}",
172 {
157 {
173 "className": 'match',
158 "className": 'match',
174 "accuracy": "complementary",
159 "accuracy": "complementary",
175 "ignorePunctuation": ":._(){}[]!'+=".split("")
160 "ignorePunctuation": ":._(){}[]!'+=".split("")
176 }
161 }
177 );
162 );
178 })
163 })
179 </script>
164 </script>
180 %endif
165 %endif
General Comments 0
You need to be logged in to leave comments. Login now