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