##// END OF EJS Templates
gravatar: add flag to show current gravatar + user as disabled user (non-active)
marcink -
r223:56e0b1a8 default
parent child Browse files
Show More
@@ -1,1892 +1,1892 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2010-2016 RhodeCode GmbH
3 # Copyright (C) 2010-2016 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 random
28 import random
29 import hashlib
29 import hashlib
30 import StringIO
30 import StringIO
31 import urllib
31 import urllib
32 import math
32 import math
33 import logging
33 import logging
34 import re
34 import re
35 import urlparse
35 import urlparse
36 import time
36 import time
37 import string
37 import string
38 import hashlib
38 import hashlib
39 import pygments
39 import pygments
40
40
41 from datetime import datetime
41 from datetime import datetime
42 from functools import partial
42 from functools import partial
43 from pygments.formatters.html import HtmlFormatter
43 from pygments.formatters.html import HtmlFormatter
44 from pygments import highlight as code_highlight
44 from pygments import highlight as code_highlight
45 from pygments.lexers import (
45 from pygments.lexers import (
46 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
46 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
47 from pylons import url
47 from pylons import url
48 from pylons.i18n.translation import _, ungettext
48 from pylons.i18n.translation import _, ungettext
49 from pyramid.threadlocal import get_current_request
49 from pyramid.threadlocal import get_current_request
50
50
51 from webhelpers.html import literal, HTML, escape
51 from webhelpers.html import literal, HTML, escape
52 from webhelpers.html.tools import *
52 from webhelpers.html.tools import *
53 from webhelpers.html.builder import make_tag
53 from webhelpers.html.builder import make_tag
54 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
54 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
55 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
55 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
56 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
56 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
57 submit, text, password, textarea, title, ul, xml_declaration, radio
57 submit, text, password, textarea, title, ul, xml_declaration, radio
58 from webhelpers.html.tools import auto_link, button_to, highlight, \
58 from webhelpers.html.tools import auto_link, button_to, highlight, \
59 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
59 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
60 from webhelpers.pylonslib import Flash as _Flash
60 from webhelpers.pylonslib import Flash as _Flash
61 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
61 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
62 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
62 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
63 replace_whitespace, urlify, truncate, wrap_paragraphs
63 replace_whitespace, urlify, truncate, wrap_paragraphs
64 from webhelpers.date import time_ago_in_words
64 from webhelpers.date import time_ago_in_words
65 from webhelpers.paginate import Page as _Page
65 from webhelpers.paginate import Page as _Page
66 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
66 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
67 convert_boolean_attrs, NotGiven, _make_safe_id_component
67 convert_boolean_attrs, NotGiven, _make_safe_id_component
68 from webhelpers2.number import format_byte_size
68 from webhelpers2.number import format_byte_size
69
69
70 from rhodecode.lib.annotate import annotate_highlight
70 from rhodecode.lib.annotate import annotate_highlight
71 from rhodecode.lib.action_parser import action_parser
71 from rhodecode.lib.action_parser import action_parser
72 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
72 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
73 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
73 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
74 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
74 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
75 AttributeDict, safe_int, md5, md5_safe
75 AttributeDict, safe_int, md5, md5_safe
76 from rhodecode.lib.markup_renderer import MarkupRenderer
76 from rhodecode.lib.markup_renderer import MarkupRenderer
77 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
77 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
78 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
78 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
79 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
79 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
80 from rhodecode.model.changeset_status import ChangesetStatusModel
80 from rhodecode.model.changeset_status import ChangesetStatusModel
81 from rhodecode.model.db import Permission, User, Repository
81 from rhodecode.model.db import Permission, User, Repository
82 from rhodecode.model.repo_group import RepoGroupModel
82 from rhodecode.model.repo_group import RepoGroupModel
83 from rhodecode.model.settings import IssueTrackerSettingsModel
83 from rhodecode.model.settings import IssueTrackerSettingsModel
84
84
85 log = logging.getLogger(__name__)
85 log = logging.getLogger(__name__)
86
86
87 DEFAULT_USER = User.DEFAULT_USER
87 DEFAULT_USER = User.DEFAULT_USER
88 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
88 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
89
89
90
90
91 def html_escape(text, html_escape_table=None):
91 def html_escape(text, html_escape_table=None):
92 """Produce entities within text."""
92 """Produce entities within text."""
93 if not html_escape_table:
93 if not html_escape_table:
94 html_escape_table = {
94 html_escape_table = {
95 "&": "&amp;",
95 "&": "&amp;",
96 '"': "&quot;",
96 '"': "&quot;",
97 "'": "&apos;",
97 "'": "&apos;",
98 ">": "&gt;",
98 ">": "&gt;",
99 "<": "&lt;",
99 "<": "&lt;",
100 }
100 }
101 return "".join(html_escape_table.get(c, c) for c in text)
101 return "".join(html_escape_table.get(c, c) for c in text)
102
102
103
103
104 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
104 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
105 """
105 """
106 Truncate string ``s`` at the first occurrence of ``sub``.
106 Truncate string ``s`` at the first occurrence of ``sub``.
107
107
108 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
108 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
109 """
109 """
110 suffix_if_chopped = suffix_if_chopped or ''
110 suffix_if_chopped = suffix_if_chopped or ''
111 pos = s.find(sub)
111 pos = s.find(sub)
112 if pos == -1:
112 if pos == -1:
113 return s
113 return s
114
114
115 if inclusive:
115 if inclusive:
116 pos += len(sub)
116 pos += len(sub)
117
117
118 chopped = s[:pos]
118 chopped = s[:pos]
119 left = s[pos:].strip()
119 left = s[pos:].strip()
120
120
121 if left and suffix_if_chopped:
121 if left and suffix_if_chopped:
122 chopped += suffix_if_chopped
122 chopped += suffix_if_chopped
123
123
124 return chopped
124 return chopped
125
125
126
126
127 def shorter(text, size=20):
127 def shorter(text, size=20):
128 postfix = '...'
128 postfix = '...'
129 if len(text) > size:
129 if len(text) > size:
130 return text[:size - len(postfix)] + postfix
130 return text[:size - len(postfix)] + postfix
131 return text
131 return text
132
132
133
133
134 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
134 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
135 """
135 """
136 Reset button
136 Reset button
137 """
137 """
138 _set_input_attrs(attrs, type, name, value)
138 _set_input_attrs(attrs, type, name, value)
139 _set_id_attr(attrs, id, name)
139 _set_id_attr(attrs, id, name)
140 convert_boolean_attrs(attrs, ["disabled"])
140 convert_boolean_attrs(attrs, ["disabled"])
141 return HTML.input(**attrs)
141 return HTML.input(**attrs)
142
142
143 reset = _reset
143 reset = _reset
144 safeid = _make_safe_id_component
144 safeid = _make_safe_id_component
145
145
146
146
147 def branding(name, length=40):
147 def branding(name, length=40):
148 return truncate(name, length, indicator="")
148 return truncate(name, length, indicator="")
149
149
150
150
151 def FID(raw_id, path):
151 def FID(raw_id, path):
152 """
152 """
153 Creates a unique ID for filenode based on it's hash of path and commit
153 Creates a unique ID for filenode based on it's hash of path and commit
154 it's safe to use in urls
154 it's safe to use in urls
155
155
156 :param raw_id:
156 :param raw_id:
157 :param path:
157 :param path:
158 """
158 """
159
159
160 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
160 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
161
161
162
162
163 class _GetError(object):
163 class _GetError(object):
164 """Get error from form_errors, and represent it as span wrapped error
164 """Get error from form_errors, and represent it as span wrapped error
165 message
165 message
166
166
167 :param field_name: field to fetch errors for
167 :param field_name: field to fetch errors for
168 :param form_errors: form errors dict
168 :param form_errors: form errors dict
169 """
169 """
170
170
171 def __call__(self, field_name, form_errors):
171 def __call__(self, field_name, form_errors):
172 tmpl = """<span class="error_msg">%s</span>"""
172 tmpl = """<span class="error_msg">%s</span>"""
173 if form_errors and field_name in form_errors:
173 if form_errors and field_name in form_errors:
174 return literal(tmpl % form_errors.get(field_name))
174 return literal(tmpl % form_errors.get(field_name))
175
175
176 get_error = _GetError()
176 get_error = _GetError()
177
177
178
178
179 class _ToolTip(object):
179 class _ToolTip(object):
180
180
181 def __call__(self, tooltip_title, trim_at=50):
181 def __call__(self, tooltip_title, trim_at=50):
182 """
182 """
183 Special function just to wrap our text into nice formatted
183 Special function just to wrap our text into nice formatted
184 autowrapped text
184 autowrapped text
185
185
186 :param tooltip_title:
186 :param tooltip_title:
187 """
187 """
188 tooltip_title = escape(tooltip_title)
188 tooltip_title = escape(tooltip_title)
189 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
189 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
190 return tooltip_title
190 return tooltip_title
191 tooltip = _ToolTip()
191 tooltip = _ToolTip()
192
192
193
193
194 def files_breadcrumbs(repo_name, commit_id, file_path):
194 def files_breadcrumbs(repo_name, commit_id, file_path):
195 if isinstance(file_path, str):
195 if isinstance(file_path, str):
196 file_path = safe_unicode(file_path)
196 file_path = safe_unicode(file_path)
197
197
198 # TODO: johbo: Is this always a url like path, or is this operating
198 # TODO: johbo: Is this always a url like path, or is this operating
199 # system dependent?
199 # system dependent?
200 path_segments = file_path.split('/')
200 path_segments = file_path.split('/')
201
201
202 repo_name_html = escape(repo_name)
202 repo_name_html = escape(repo_name)
203 if len(path_segments) == 1 and path_segments[0] == '':
203 if len(path_segments) == 1 and path_segments[0] == '':
204 url_segments = [repo_name_html]
204 url_segments = [repo_name_html]
205 else:
205 else:
206 url_segments = [
206 url_segments = [
207 link_to(
207 link_to(
208 repo_name_html,
208 repo_name_html,
209 url('files_home',
209 url('files_home',
210 repo_name=repo_name,
210 repo_name=repo_name,
211 revision=commit_id,
211 revision=commit_id,
212 f_path=''),
212 f_path=''),
213 class_='pjax-link')]
213 class_='pjax-link')]
214
214
215 last_cnt = len(path_segments) - 1
215 last_cnt = len(path_segments) - 1
216 for cnt, segment in enumerate(path_segments):
216 for cnt, segment in enumerate(path_segments):
217 if not segment:
217 if not segment:
218 continue
218 continue
219 segment_html = escape(segment)
219 segment_html = escape(segment)
220
220
221 if cnt != last_cnt:
221 if cnt != last_cnt:
222 url_segments.append(
222 url_segments.append(
223 link_to(
223 link_to(
224 segment_html,
224 segment_html,
225 url('files_home',
225 url('files_home',
226 repo_name=repo_name,
226 repo_name=repo_name,
227 revision=commit_id,
227 revision=commit_id,
228 f_path='/'.join(path_segments[:cnt + 1])),
228 f_path='/'.join(path_segments[:cnt + 1])),
229 class_='pjax-link'))
229 class_='pjax-link'))
230 else:
230 else:
231 url_segments.append(segment_html)
231 url_segments.append(segment_html)
232
232
233 return literal('/'.join(url_segments))
233 return literal('/'.join(url_segments))
234
234
235
235
236 class CodeHtmlFormatter(HtmlFormatter):
236 class CodeHtmlFormatter(HtmlFormatter):
237 """
237 """
238 My code Html Formatter for source codes
238 My code Html Formatter for source codes
239 """
239 """
240
240
241 def wrap(self, source, outfile):
241 def wrap(self, source, outfile):
242 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
242 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
243
243
244 def _wrap_code(self, source):
244 def _wrap_code(self, source):
245 for cnt, it in enumerate(source):
245 for cnt, it in enumerate(source):
246 i, t = it
246 i, t = it
247 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
247 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
248 yield i, t
248 yield i, t
249
249
250 def _wrap_tablelinenos(self, inner):
250 def _wrap_tablelinenos(self, inner):
251 dummyoutfile = StringIO.StringIO()
251 dummyoutfile = StringIO.StringIO()
252 lncount = 0
252 lncount = 0
253 for t, line in inner:
253 for t, line in inner:
254 if t:
254 if t:
255 lncount += 1
255 lncount += 1
256 dummyoutfile.write(line)
256 dummyoutfile.write(line)
257
257
258 fl = self.linenostart
258 fl = self.linenostart
259 mw = len(str(lncount + fl - 1))
259 mw = len(str(lncount + fl - 1))
260 sp = self.linenospecial
260 sp = self.linenospecial
261 st = self.linenostep
261 st = self.linenostep
262 la = self.lineanchors
262 la = self.lineanchors
263 aln = self.anchorlinenos
263 aln = self.anchorlinenos
264 nocls = self.noclasses
264 nocls = self.noclasses
265 if sp:
265 if sp:
266 lines = []
266 lines = []
267
267
268 for i in range(fl, fl + lncount):
268 for i in range(fl, fl + lncount):
269 if i % st == 0:
269 if i % st == 0:
270 if i % sp == 0:
270 if i % sp == 0:
271 if aln:
271 if aln:
272 lines.append('<a href="#%s%d" class="special">%*d</a>' %
272 lines.append('<a href="#%s%d" class="special">%*d</a>' %
273 (la, i, mw, i))
273 (la, i, mw, i))
274 else:
274 else:
275 lines.append('<span class="special">%*d</span>' % (mw, i))
275 lines.append('<span class="special">%*d</span>' % (mw, i))
276 else:
276 else:
277 if aln:
277 if aln:
278 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
278 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
279 else:
279 else:
280 lines.append('%*d' % (mw, i))
280 lines.append('%*d' % (mw, i))
281 else:
281 else:
282 lines.append('')
282 lines.append('')
283 ls = '\n'.join(lines)
283 ls = '\n'.join(lines)
284 else:
284 else:
285 lines = []
285 lines = []
286 for i in range(fl, fl + lncount):
286 for i in range(fl, fl + lncount):
287 if i % st == 0:
287 if i % st == 0:
288 if aln:
288 if aln:
289 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
289 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
290 else:
290 else:
291 lines.append('%*d' % (mw, i))
291 lines.append('%*d' % (mw, i))
292 else:
292 else:
293 lines.append('')
293 lines.append('')
294 ls = '\n'.join(lines)
294 ls = '\n'.join(lines)
295
295
296 # in case you wonder about the seemingly redundant <div> here: since the
296 # in case you wonder about the seemingly redundant <div> here: since the
297 # content in the other cell also is wrapped in a div, some browsers in
297 # content in the other cell also is wrapped in a div, some browsers in
298 # some configurations seem to mess up the formatting...
298 # some configurations seem to mess up the formatting...
299 if nocls:
299 if nocls:
300 yield 0, ('<table class="%stable">' % self.cssclass +
300 yield 0, ('<table class="%stable">' % self.cssclass +
301 '<tr><td><div class="linenodiv" '
301 '<tr><td><div class="linenodiv" '
302 'style="background-color: #f0f0f0; padding-right: 10px">'
302 'style="background-color: #f0f0f0; padding-right: 10px">'
303 '<pre style="line-height: 125%">' +
303 '<pre style="line-height: 125%">' +
304 ls + '</pre></div></td><td id="hlcode" class="code">')
304 ls + '</pre></div></td><td id="hlcode" class="code">')
305 else:
305 else:
306 yield 0, ('<table class="%stable">' % self.cssclass +
306 yield 0, ('<table class="%stable">' % self.cssclass +
307 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
307 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
308 ls + '</pre></div></td><td id="hlcode" class="code">')
308 ls + '</pre></div></td><td id="hlcode" class="code">')
309 yield 0, dummyoutfile.getvalue()
309 yield 0, dummyoutfile.getvalue()
310 yield 0, '</td></tr></table>'
310 yield 0, '</td></tr></table>'
311
311
312
312
313 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
313 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
314 def __init__(self, **kw):
314 def __init__(self, **kw):
315 # only show these line numbers if set
315 # only show these line numbers if set
316 self.only_lines = kw.pop('only_line_numbers', [])
316 self.only_lines = kw.pop('only_line_numbers', [])
317 self.query_terms = kw.pop('query_terms', [])
317 self.query_terms = kw.pop('query_terms', [])
318 self.max_lines = kw.pop('max_lines', 5)
318 self.max_lines = kw.pop('max_lines', 5)
319 self.line_context = kw.pop('line_context', 3)
319 self.line_context = kw.pop('line_context', 3)
320 self.url = kw.pop('url', None)
320 self.url = kw.pop('url', None)
321
321
322 super(CodeHtmlFormatter, self).__init__(**kw)
322 super(CodeHtmlFormatter, self).__init__(**kw)
323
323
324 def _wrap_code(self, source):
324 def _wrap_code(self, source):
325 for cnt, it in enumerate(source):
325 for cnt, it in enumerate(source):
326 i, t = it
326 i, t = it
327 t = '<pre>%s</pre>' % t
327 t = '<pre>%s</pre>' % t
328 yield i, t
328 yield i, t
329
329
330 def _wrap_tablelinenos(self, inner):
330 def _wrap_tablelinenos(self, inner):
331 yield 0, '<table class="code-highlight %stable">' % self.cssclass
331 yield 0, '<table class="code-highlight %stable">' % self.cssclass
332
332
333 last_shown_line_number = 0
333 last_shown_line_number = 0
334 current_line_number = 1
334 current_line_number = 1
335
335
336 for t, line in inner:
336 for t, line in inner:
337 if not t:
337 if not t:
338 yield t, line
338 yield t, line
339 continue
339 continue
340
340
341 if current_line_number in self.only_lines:
341 if current_line_number in self.only_lines:
342 if last_shown_line_number + 1 != current_line_number:
342 if last_shown_line_number + 1 != current_line_number:
343 yield 0, '<tr>'
343 yield 0, '<tr>'
344 yield 0, '<td class="line">...</td>'
344 yield 0, '<td class="line">...</td>'
345 yield 0, '<td id="hlcode" class="code"></td>'
345 yield 0, '<td id="hlcode" class="code"></td>'
346 yield 0, '</tr>'
346 yield 0, '</tr>'
347
347
348 yield 0, '<tr>'
348 yield 0, '<tr>'
349 if self.url:
349 if self.url:
350 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
350 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
351 self.url, current_line_number, current_line_number)
351 self.url, current_line_number, current_line_number)
352 else:
352 else:
353 yield 0, '<td class="line"><a href="">%i</a></td>' % (
353 yield 0, '<td class="line"><a href="">%i</a></td>' % (
354 current_line_number)
354 current_line_number)
355 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
355 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
356 yield 0, '</tr>'
356 yield 0, '</tr>'
357
357
358 last_shown_line_number = current_line_number
358 last_shown_line_number = current_line_number
359
359
360 current_line_number += 1
360 current_line_number += 1
361
361
362
362
363 yield 0, '</table>'
363 yield 0, '</table>'
364
364
365
365
366 def extract_phrases(text_query):
366 def extract_phrases(text_query):
367 """
367 """
368 Extracts phrases from search term string making sure phrases
368 Extracts phrases from search term string making sure phrases
369 contained in double quotes are kept together - and discarding empty values
369 contained in double quotes are kept together - and discarding empty values
370 or fully whitespace values eg.
370 or fully whitespace values eg.
371
371
372 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more']
372 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more']
373
373
374 """
374 """
375
375
376 in_phrase = False
376 in_phrase = False
377 buf = ''
377 buf = ''
378 phrases = []
378 phrases = []
379 for char in text_query:
379 for char in text_query:
380 if in_phrase:
380 if in_phrase:
381 if char == '"': # end phrase
381 if char == '"': # end phrase
382 phrases.append(buf)
382 phrases.append(buf)
383 buf = ''
383 buf = ''
384 in_phrase = False
384 in_phrase = False
385 continue
385 continue
386 else:
386 else:
387 buf += char
387 buf += char
388 continue
388 continue
389 else:
389 else:
390 if char == '"': # start phrase
390 if char == '"': # start phrase
391 in_phrase = True
391 in_phrase = True
392 phrases.append(buf)
392 phrases.append(buf)
393 buf = ''
393 buf = ''
394 continue
394 continue
395 elif char == ' ':
395 elif char == ' ':
396 phrases.append(buf)
396 phrases.append(buf)
397 buf = ''
397 buf = ''
398 continue
398 continue
399 else:
399 else:
400 buf += char
400 buf += char
401
401
402 phrases.append(buf)
402 phrases.append(buf)
403 phrases = [phrase.strip() for phrase in phrases if phrase.strip()]
403 phrases = [phrase.strip() for phrase in phrases if phrase.strip()]
404 return phrases
404 return phrases
405
405
406
406
407 def get_matching_offsets(text, phrases):
407 def get_matching_offsets(text, phrases):
408 """
408 """
409 Returns a list of string offsets in `text` that the list of `terms` match
409 Returns a list of string offsets in `text` that the list of `terms` match
410
410
411 >>> get_matching_offsets('some text here', ['some', 'here'])
411 >>> get_matching_offsets('some text here', ['some', 'here'])
412 [(0, 4), (10, 14)]
412 [(0, 4), (10, 14)]
413
413
414 """
414 """
415 offsets = []
415 offsets = []
416 for phrase in phrases:
416 for phrase in phrases:
417 for match in re.finditer(phrase, text):
417 for match in re.finditer(phrase, text):
418 offsets.append((match.start(), match.end()))
418 offsets.append((match.start(), match.end()))
419
419
420 return offsets
420 return offsets
421
421
422
422
423 def normalize_text_for_matching(x):
423 def normalize_text_for_matching(x):
424 """
424 """
425 Replaces all non alnum characters to spaces and lower cases the string,
425 Replaces all non alnum characters to spaces and lower cases the string,
426 useful for comparing two text strings without punctuation
426 useful for comparing two text strings without punctuation
427 """
427 """
428 return re.sub(r'[^\w]', ' ', x.lower())
428 return re.sub(r'[^\w]', ' ', x.lower())
429
429
430
430
431 def get_matching_line_offsets(lines, terms):
431 def get_matching_line_offsets(lines, terms):
432 """ Return a set of `lines` indices (starting from 1) matching a
432 """ Return a set of `lines` indices (starting from 1) matching a
433 text search query, along with `context` lines above/below matching lines
433 text search query, along with `context` lines above/below matching lines
434
434
435 :param lines: list of strings representing lines
435 :param lines: list of strings representing lines
436 :param terms: search term string to match in lines eg. 'some text'
436 :param terms: search term string to match in lines eg. 'some text'
437 :param context: number of lines above/below a matching line to add to result
437 :param context: number of lines above/below a matching line to add to result
438 :param max_lines: cut off for lines of interest
438 :param max_lines: cut off for lines of interest
439 eg.
439 eg.
440
440
441 >>> get_matching_line_offsets('''
441 >>> get_matching_line_offsets('''
442 words words words
442 words words words
443 words words words
443 words words words
444 some text some
444 some text some
445 words words words
445 words words words
446 words words words
446 words words words
447 text here what
447 text here what
448 ''', 'text', context=1)
448 ''', 'text', context=1)
449 {3: [(5, 9)], 6: [(0, 4)]]
449 {3: [(5, 9)], 6: [(0, 4)]]
450 """
450 """
451 matching_lines = {}
451 matching_lines = {}
452 phrases = [normalize_text_for_matching(phrase)
452 phrases = [normalize_text_for_matching(phrase)
453 for phrase in extract_phrases(terms)]
453 for phrase in extract_phrases(terms)]
454
454
455 for line_index, line in enumerate(lines, start=1):
455 for line_index, line in enumerate(lines, start=1):
456 match_offsets = get_matching_offsets(
456 match_offsets = get_matching_offsets(
457 normalize_text_for_matching(line), phrases)
457 normalize_text_for_matching(line), phrases)
458 if match_offsets:
458 if match_offsets:
459 matching_lines[line_index] = match_offsets
459 matching_lines[line_index] = match_offsets
460
460
461 return matching_lines
461 return matching_lines
462
462
463 def get_lexer_safe(mimetype=None, filepath=None):
463 def get_lexer_safe(mimetype=None, filepath=None):
464 """
464 """
465 Tries to return a relevant pygments lexer using mimetype/filepath name,
465 Tries to return a relevant pygments lexer using mimetype/filepath name,
466 defaulting to plain text if none could be found
466 defaulting to plain text if none could be found
467 """
467 """
468 lexer = None
468 lexer = None
469 try:
469 try:
470 if mimetype:
470 if mimetype:
471 lexer = get_lexer_for_mimetype(mimetype)
471 lexer = get_lexer_for_mimetype(mimetype)
472 if not lexer:
472 if not lexer:
473 lexer = get_lexer_for_filename(path)
473 lexer = get_lexer_for_filename(path)
474 except pygments.util.ClassNotFound:
474 except pygments.util.ClassNotFound:
475 pass
475 pass
476
476
477 if not lexer:
477 if not lexer:
478 lexer = get_lexer_by_name('text')
478 lexer = get_lexer_by_name('text')
479
479
480 return lexer
480 return lexer
481
481
482
482
483 def pygmentize(filenode, **kwargs):
483 def pygmentize(filenode, **kwargs):
484 """
484 """
485 pygmentize function using pygments
485 pygmentize function using pygments
486
486
487 :param filenode:
487 :param filenode:
488 """
488 """
489 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
489 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
490 return literal(code_highlight(filenode.content, lexer,
490 return literal(code_highlight(filenode.content, lexer,
491 CodeHtmlFormatter(**kwargs)))
491 CodeHtmlFormatter(**kwargs)))
492
492
493
493
494 def pygmentize_annotation(repo_name, filenode, **kwargs):
494 def pygmentize_annotation(repo_name, filenode, **kwargs):
495 """
495 """
496 pygmentize function for annotation
496 pygmentize function for annotation
497
497
498 :param filenode:
498 :param filenode:
499 """
499 """
500
500
501 color_dict = {}
501 color_dict = {}
502
502
503 def gen_color(n=10000):
503 def gen_color(n=10000):
504 """generator for getting n of evenly distributed colors using
504 """generator for getting n of evenly distributed colors using
505 hsv color and golden ratio. It always return same order of colors
505 hsv color and golden ratio. It always return same order of colors
506
506
507 :returns: RGB tuple
507 :returns: RGB tuple
508 """
508 """
509
509
510 def hsv_to_rgb(h, s, v):
510 def hsv_to_rgb(h, s, v):
511 if s == 0.0:
511 if s == 0.0:
512 return v, v, v
512 return v, v, v
513 i = int(h * 6.0) # XXX assume int() truncates!
513 i = int(h * 6.0) # XXX assume int() truncates!
514 f = (h * 6.0) - i
514 f = (h * 6.0) - i
515 p = v * (1.0 - s)
515 p = v * (1.0 - s)
516 q = v * (1.0 - s * f)
516 q = v * (1.0 - s * f)
517 t = v * (1.0 - s * (1.0 - f))
517 t = v * (1.0 - s * (1.0 - f))
518 i = i % 6
518 i = i % 6
519 if i == 0:
519 if i == 0:
520 return v, t, p
520 return v, t, p
521 if i == 1:
521 if i == 1:
522 return q, v, p
522 return q, v, p
523 if i == 2:
523 if i == 2:
524 return p, v, t
524 return p, v, t
525 if i == 3:
525 if i == 3:
526 return p, q, v
526 return p, q, v
527 if i == 4:
527 if i == 4:
528 return t, p, v
528 return t, p, v
529 if i == 5:
529 if i == 5:
530 return v, p, q
530 return v, p, q
531
531
532 golden_ratio = 0.618033988749895
532 golden_ratio = 0.618033988749895
533 h = 0.22717784590367374
533 h = 0.22717784590367374
534
534
535 for _ in xrange(n):
535 for _ in xrange(n):
536 h += golden_ratio
536 h += golden_ratio
537 h %= 1
537 h %= 1
538 HSV_tuple = [h, 0.95, 0.95]
538 HSV_tuple = [h, 0.95, 0.95]
539 RGB_tuple = hsv_to_rgb(*HSV_tuple)
539 RGB_tuple = hsv_to_rgb(*HSV_tuple)
540 yield map(lambda x: str(int(x * 256)), RGB_tuple)
540 yield map(lambda x: str(int(x * 256)), RGB_tuple)
541
541
542 cgenerator = gen_color()
542 cgenerator = gen_color()
543
543
544 def get_color_string(commit_id):
544 def get_color_string(commit_id):
545 if commit_id in color_dict:
545 if commit_id in color_dict:
546 col = color_dict[commit_id]
546 col = color_dict[commit_id]
547 else:
547 else:
548 col = color_dict[commit_id] = cgenerator.next()
548 col = color_dict[commit_id] = cgenerator.next()
549 return "color: rgb(%s)! important;" % (', '.join(col))
549 return "color: rgb(%s)! important;" % (', '.join(col))
550
550
551 def url_func(repo_name):
551 def url_func(repo_name):
552
552
553 def _url_func(commit):
553 def _url_func(commit):
554 author = commit.author
554 author = commit.author
555 date = commit.date
555 date = commit.date
556 message = tooltip(commit.message)
556 message = tooltip(commit.message)
557
557
558 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
558 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
559 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
559 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
560 "</b> %s<br/></div>")
560 "</b> %s<br/></div>")
561
561
562 tooltip_html = tooltip_html % (author, date, message)
562 tooltip_html = tooltip_html % (author, date, message)
563 lnk_format = '%5s:%s' % ('r%s' % commit.idx, commit.short_id)
563 lnk_format = '%5s:%s' % ('r%s' % commit.idx, commit.short_id)
564 uri = link_to(
564 uri = link_to(
565 lnk_format,
565 lnk_format,
566 url('changeset_home', repo_name=repo_name,
566 url('changeset_home', repo_name=repo_name,
567 revision=commit.raw_id),
567 revision=commit.raw_id),
568 style=get_color_string(commit.raw_id),
568 style=get_color_string(commit.raw_id),
569 class_='tooltip',
569 class_='tooltip',
570 title=tooltip_html
570 title=tooltip_html
571 )
571 )
572
572
573 uri += '\n'
573 uri += '\n'
574 return uri
574 return uri
575 return _url_func
575 return _url_func
576
576
577 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
577 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
578
578
579
579
580 def is_following_repo(repo_name, user_id):
580 def is_following_repo(repo_name, user_id):
581 from rhodecode.model.scm import ScmModel
581 from rhodecode.model.scm import ScmModel
582 return ScmModel().is_following_repo(repo_name, user_id)
582 return ScmModel().is_following_repo(repo_name, user_id)
583
583
584
584
585 class _Message(object):
585 class _Message(object):
586 """A message returned by ``Flash.pop_messages()``.
586 """A message returned by ``Flash.pop_messages()``.
587
587
588 Converting the message to a string returns the message text. Instances
588 Converting the message to a string returns the message text. Instances
589 also have the following attributes:
589 also have the following attributes:
590
590
591 * ``message``: the message text.
591 * ``message``: the message text.
592 * ``category``: the category specified when the message was created.
592 * ``category``: the category specified when the message was created.
593 """
593 """
594
594
595 def __init__(self, category, message):
595 def __init__(self, category, message):
596 self.category = category
596 self.category = category
597 self.message = message
597 self.message = message
598
598
599 def __str__(self):
599 def __str__(self):
600 return self.message
600 return self.message
601
601
602 __unicode__ = __str__
602 __unicode__ = __str__
603
603
604 def __html__(self):
604 def __html__(self):
605 return escape(safe_unicode(self.message))
605 return escape(safe_unicode(self.message))
606
606
607
607
608 class Flash(_Flash):
608 class Flash(_Flash):
609
609
610 def pop_messages(self):
610 def pop_messages(self):
611 """Return all accumulated messages and delete them from the session.
611 """Return all accumulated messages and delete them from the session.
612
612
613 The return value is a list of ``Message`` objects.
613 The return value is a list of ``Message`` objects.
614 """
614 """
615 from pylons import session
615 from pylons import session
616
616
617 messages = []
617 messages = []
618
618
619 # Pop the 'old' pylons flash messages. They are tuples of the form
619 # Pop the 'old' pylons flash messages. They are tuples of the form
620 # (category, message)
620 # (category, message)
621 for cat, msg in session.pop(self.session_key, []):
621 for cat, msg in session.pop(self.session_key, []):
622 messages.append(_Message(cat, msg))
622 messages.append(_Message(cat, msg))
623
623
624 # Pop the 'new' pyramid flash messages for each category as list
624 # Pop the 'new' pyramid flash messages for each category as list
625 # of strings.
625 # of strings.
626 for cat in self.categories:
626 for cat in self.categories:
627 for msg in session.pop_flash(queue=cat):
627 for msg in session.pop_flash(queue=cat):
628 messages.append(_Message(cat, msg))
628 messages.append(_Message(cat, msg))
629 # Map messages from the default queue to the 'notice' category.
629 # Map messages from the default queue to the 'notice' category.
630 for msg in session.pop_flash():
630 for msg in session.pop_flash():
631 messages.append(_Message('notice', msg))
631 messages.append(_Message('notice', msg))
632
632
633 session.save()
633 session.save()
634 return messages
634 return messages
635
635
636 flash = Flash()
636 flash = Flash()
637
637
638 #==============================================================================
638 #==============================================================================
639 # SCM FILTERS available via h.
639 # SCM FILTERS available via h.
640 #==============================================================================
640 #==============================================================================
641 from rhodecode.lib.vcs.utils import author_name, author_email
641 from rhodecode.lib.vcs.utils import author_name, author_email
642 from rhodecode.lib.utils2 import credentials_filter, age as _age
642 from rhodecode.lib.utils2 import credentials_filter, age as _age
643 from rhodecode.model.db import User, ChangesetStatus
643 from rhodecode.model.db import User, ChangesetStatus
644
644
645 age = _age
645 age = _age
646 capitalize = lambda x: x.capitalize()
646 capitalize = lambda x: x.capitalize()
647 email = author_email
647 email = author_email
648 short_id = lambda x: x[:12]
648 short_id = lambda x: x[:12]
649 hide_credentials = lambda x: ''.join(credentials_filter(x))
649 hide_credentials = lambda x: ''.join(credentials_filter(x))
650
650
651
651
652 def age_component(datetime_iso, value=None, time_is_local=False):
652 def age_component(datetime_iso, value=None, time_is_local=False):
653 title = value or format_date(datetime_iso)
653 title = value or format_date(datetime_iso)
654
654
655 # detect if we have a timezone info, otherwise, add it
655 # detect if we have a timezone info, otherwise, add it
656 if isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
656 if isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
657 tzinfo = '+00:00'
657 tzinfo = '+00:00'
658
658
659 if time_is_local:
659 if time_is_local:
660 tzinfo = time.strftime("+%H:%M",
660 tzinfo = time.strftime("+%H:%M",
661 time.gmtime(
661 time.gmtime(
662 (datetime.now() - datetime.utcnow()).seconds + 1
662 (datetime.now() - datetime.utcnow()).seconds + 1
663 )
663 )
664 )
664 )
665
665
666 return literal(
666 return literal(
667 '<time class="timeago tooltip" '
667 '<time class="timeago tooltip" '
668 'title="{1}" datetime="{0}{2}">{1}</time>'.format(
668 'title="{1}" datetime="{0}{2}">{1}</time>'.format(
669 datetime_iso, title, tzinfo))
669 datetime_iso, title, tzinfo))
670
670
671
671
672 def _shorten_commit_id(commit_id):
672 def _shorten_commit_id(commit_id):
673 from rhodecode import CONFIG
673 from rhodecode import CONFIG
674 def_len = safe_int(CONFIG.get('rhodecode_show_sha_length', 12))
674 def_len = safe_int(CONFIG.get('rhodecode_show_sha_length', 12))
675 return commit_id[:def_len]
675 return commit_id[:def_len]
676
676
677
677
678 def get_repo_id_from_name(repo_name):
678 def get_repo_id_from_name(repo_name):
679 repo = get_by_repo_name(repo_name)
679 repo = get_by_repo_name(repo_name)
680 return repo.repo_id
680 return repo.repo_id
681
681
682
682
683 def show_id(commit):
683 def show_id(commit):
684 """
684 """
685 Configurable function that shows ID
685 Configurable function that shows ID
686 by default it's r123:fffeeefffeee
686 by default it's r123:fffeeefffeee
687
687
688 :param commit: commit instance
688 :param commit: commit instance
689 """
689 """
690 from rhodecode import CONFIG
690 from rhodecode import CONFIG
691 show_idx = str2bool(CONFIG.get('rhodecode_show_revision_number', True))
691 show_idx = str2bool(CONFIG.get('rhodecode_show_revision_number', True))
692
692
693 raw_id = _shorten_commit_id(commit.raw_id)
693 raw_id = _shorten_commit_id(commit.raw_id)
694 if show_idx:
694 if show_idx:
695 return 'r%s:%s' % (commit.idx, raw_id)
695 return 'r%s:%s' % (commit.idx, raw_id)
696 else:
696 else:
697 return '%s' % (raw_id, )
697 return '%s' % (raw_id, )
698
698
699
699
700 def format_date(date):
700 def format_date(date):
701 """
701 """
702 use a standardized formatting for dates used in RhodeCode
702 use a standardized formatting for dates used in RhodeCode
703
703
704 :param date: date/datetime object
704 :param date: date/datetime object
705 :return: formatted date
705 :return: formatted date
706 """
706 """
707
707
708 if date:
708 if date:
709 _fmt = "%a, %d %b %Y %H:%M:%S"
709 _fmt = "%a, %d %b %Y %H:%M:%S"
710 return safe_unicode(date.strftime(_fmt))
710 return safe_unicode(date.strftime(_fmt))
711
711
712 return u""
712 return u""
713
713
714
714
715 class _RepoChecker(object):
715 class _RepoChecker(object):
716
716
717 def __init__(self, backend_alias):
717 def __init__(self, backend_alias):
718 self._backend_alias = backend_alias
718 self._backend_alias = backend_alias
719
719
720 def __call__(self, repository):
720 def __call__(self, repository):
721 if hasattr(repository, 'alias'):
721 if hasattr(repository, 'alias'):
722 _type = repository.alias
722 _type = repository.alias
723 elif hasattr(repository, 'repo_type'):
723 elif hasattr(repository, 'repo_type'):
724 _type = repository.repo_type
724 _type = repository.repo_type
725 else:
725 else:
726 _type = repository
726 _type = repository
727 return _type == self._backend_alias
727 return _type == self._backend_alias
728
728
729 is_git = _RepoChecker('git')
729 is_git = _RepoChecker('git')
730 is_hg = _RepoChecker('hg')
730 is_hg = _RepoChecker('hg')
731 is_svn = _RepoChecker('svn')
731 is_svn = _RepoChecker('svn')
732
732
733
733
734 def get_repo_type_by_name(repo_name):
734 def get_repo_type_by_name(repo_name):
735 repo = Repository.get_by_repo_name(repo_name)
735 repo = Repository.get_by_repo_name(repo_name)
736 return repo.repo_type
736 return repo.repo_type
737
737
738
738
739 def is_svn_without_proxy(repository):
739 def is_svn_without_proxy(repository):
740 from rhodecode import CONFIG
740 from rhodecode import CONFIG
741 if is_svn(repository):
741 if is_svn(repository):
742 if not CONFIG.get('rhodecode_proxy_subversion_http_requests', False):
742 if not CONFIG.get('rhodecode_proxy_subversion_http_requests', False):
743 return True
743 return True
744 return False
744 return False
745
745
746
746
747 def email_or_none(author):
747 def email_or_none(author):
748 # extract email from the commit string
748 # extract email from the commit string
749 _email = author_email(author)
749 _email = author_email(author)
750 if _email != '':
750 if _email != '':
751 # check it against RhodeCode database, and use the MAIN email for this
751 # check it against RhodeCode database, and use the MAIN email for this
752 # user
752 # user
753 user = User.get_by_email(_email, case_insensitive=True, cache=True)
753 user = User.get_by_email(_email, case_insensitive=True, cache=True)
754 if user is not None:
754 if user is not None:
755 return user.email
755 return user.email
756 return _email
756 return _email
757
757
758 # See if it contains a username we can get an email from
758 # See if it contains a username we can get an email from
759 user = User.get_by_username(author_name(author), case_insensitive=True,
759 user = User.get_by_username(author_name(author), case_insensitive=True,
760 cache=True)
760 cache=True)
761 if user is not None:
761 if user is not None:
762 return user.email
762 return user.email
763
763
764 # No valid email, not a valid user in the system, none!
764 # No valid email, not a valid user in the system, none!
765 return None
765 return None
766
766
767
767
768 def discover_user(author):
768 def discover_user(author):
769 # if author is already an instance use it for extraction
769 # if author is already an instance use it for extraction
770 if isinstance(author, User):
770 if isinstance(author, User):
771 return author
771 return author
772
772
773 # Valid email in the attribute passed, see if they're in the system
773 # Valid email in the attribute passed, see if they're in the system
774 _email = email(author)
774 _email = email(author)
775 if _email != '':
775 if _email != '':
776 user = User.get_by_email(_email, case_insensitive=True, cache=True)
776 user = User.get_by_email(_email, case_insensitive=True, cache=True)
777 if user is not None:
777 if user is not None:
778 return user
778 return user
779
779
780 # Maybe it's a username?
780 # Maybe it's a username?
781 _author = author_name(author)
781 _author = author_name(author)
782 user = User.get_by_username(_author, case_insensitive=True,
782 user = User.get_by_username(_author, case_insensitive=True,
783 cache=True)
783 cache=True)
784 if user is not None:
784 if user is not None:
785 return user
785 return user
786
786
787 return None
787 return None
788
788
789
789
790 def link_to_user(author, length=0, **kwargs):
790 def link_to_user(author, length=0, **kwargs):
791 user = discover_user(author)
791 user = discover_user(author)
792 display_person = person(author, 'username_or_name_or_email')
792 display_person = person(author, 'username_or_name_or_email')
793 if length:
793 if length:
794 display_person = shorter(display_person, length)
794 display_person = shorter(display_person, length)
795
795
796 if user:
796 if user:
797 return link_to(
797 return link_to(
798 escape(display_person),
798 escape(display_person),
799 url('user_profile', username=user.username),
799 url('user_profile', username=user.username),
800 **kwargs)
800 **kwargs)
801 else:
801 else:
802 return escape(display_person)
802 return escape(display_person)
803
803
804
804
805 def person(author, show_attr="username_and_name"):
805 def person(author, show_attr="username_and_name"):
806 # attr to return from fetched user
806 # attr to return from fetched user
807 person_getter = lambda usr: getattr(usr, show_attr)
807 person_getter = lambda usr: getattr(usr, show_attr)
808 user = discover_user(author)
808 user = discover_user(author)
809 if user:
809 if user:
810 return person_getter(user)
810 return person_getter(user)
811 else:
811 else:
812 _author = author_name(author)
812 _author = author_name(author)
813 _email = email(author)
813 _email = email(author)
814 return _author or _email
814 return _author or _email
815
815
816
816
817 def person_by_id(id_, show_attr="username_and_name"):
817 def person_by_id(id_, show_attr="username_and_name"):
818 # attr to return from fetched user
818 # attr to return from fetched user
819 person_getter = lambda usr: getattr(usr, show_attr)
819 person_getter = lambda usr: getattr(usr, show_attr)
820
820
821 #maybe it's an ID ?
821 #maybe it's an ID ?
822 if str(id_).isdigit() or isinstance(id_, int):
822 if str(id_).isdigit() or isinstance(id_, int):
823 id_ = int(id_)
823 id_ = int(id_)
824 user = User.get(id_)
824 user = User.get(id_)
825 if user is not None:
825 if user is not None:
826 return person_getter(user)
826 return person_getter(user)
827 return id_
827 return id_
828
828
829
829
830 def gravatar_with_user(author):
830 def gravatar_with_user(author, show_disabled=False):
831 from rhodecode.lib.utils import PartialRenderer
831 from rhodecode.lib.utils import PartialRenderer
832 _render = PartialRenderer('base/base.html')
832 _render = PartialRenderer('base/base.html')
833 return _render('gravatar_with_user', author)
833 return _render('gravatar_with_user', author, show_disabled=show_disabled)
834
834
835
835
836 def desc_stylize(value):
836 def desc_stylize(value):
837 """
837 """
838 converts tags from value into html equivalent
838 converts tags from value into html equivalent
839
839
840 :param value:
840 :param value:
841 """
841 """
842 if not value:
842 if not value:
843 return ''
843 return ''
844
844
845 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
845 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
846 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
846 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
847 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
847 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
848 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
848 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
849 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
849 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
850 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
850 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
851 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
851 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
852 '<div class="metatag" tag="lang">\\2</div>', value)
852 '<div class="metatag" tag="lang">\\2</div>', value)
853 value = re.sub(r'\[([a-z]+)\]',
853 value = re.sub(r'\[([a-z]+)\]',
854 '<div class="metatag" tag="\\1">\\1</div>', value)
854 '<div class="metatag" tag="\\1">\\1</div>', value)
855
855
856 return value
856 return value
857
857
858
858
859 def escaped_stylize(value):
859 def escaped_stylize(value):
860 """
860 """
861 converts tags from value into html equivalent, but escaping its value first
861 converts tags from value into html equivalent, but escaping its value first
862 """
862 """
863 if not value:
863 if not value:
864 return ''
864 return ''
865
865
866 # Using default webhelper escape method, but has to force it as a
866 # Using default webhelper escape method, but has to force it as a
867 # plain unicode instead of a markup tag to be used in regex expressions
867 # plain unicode instead of a markup tag to be used in regex expressions
868 value = unicode(escape(safe_unicode(value)))
868 value = unicode(escape(safe_unicode(value)))
869
869
870 value = re.sub(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
870 value = re.sub(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
871 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
871 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
872 value = re.sub(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
872 value = re.sub(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
873 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
873 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
874 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]',
874 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]',
875 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
875 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
876 value = re.sub(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+]*)\]',
876 value = re.sub(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+]*)\]',
877 '<div class="metatag" tag="lang">\\2</div>', value)
877 '<div class="metatag" tag="lang">\\2</div>', value)
878 value = re.sub(r'\[([a-z]+)\]',
878 value = re.sub(r'\[([a-z]+)\]',
879 '<div class="metatag" tag="\\1">\\1</div>', value)
879 '<div class="metatag" tag="\\1">\\1</div>', value)
880
880
881 return value
881 return value
882
882
883
883
884 def bool2icon(value):
884 def bool2icon(value):
885 """
885 """
886 Returns boolean value of a given value, represented as html element with
886 Returns boolean value of a given value, represented as html element with
887 classes that will represent icons
887 classes that will represent icons
888
888
889 :param value: given value to convert to html node
889 :param value: given value to convert to html node
890 """
890 """
891
891
892 if value: # does bool conversion
892 if value: # does bool conversion
893 return HTML.tag('i', class_="icon-true")
893 return HTML.tag('i', class_="icon-true")
894 else: # not true as bool
894 else: # not true as bool
895 return HTML.tag('i', class_="icon-false")
895 return HTML.tag('i', class_="icon-false")
896
896
897
897
898 #==============================================================================
898 #==============================================================================
899 # PERMS
899 # PERMS
900 #==============================================================================
900 #==============================================================================
901 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
901 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
902 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
902 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
903 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token
903 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token
904
904
905
905
906 #==============================================================================
906 #==============================================================================
907 # GRAVATAR URL
907 # GRAVATAR URL
908 #==============================================================================
908 #==============================================================================
909 class InitialsGravatar(object):
909 class InitialsGravatar(object):
910 def __init__(self, email_address, first_name, last_name, size=30,
910 def __init__(self, email_address, first_name, last_name, size=30,
911 background=None, text_color='#fff'):
911 background=None, text_color='#fff'):
912 self.size = size
912 self.size = size
913 self.first_name = first_name
913 self.first_name = first_name
914 self.last_name = last_name
914 self.last_name = last_name
915 self.email_address = email_address
915 self.email_address = email_address
916 self.background = background or self.str2color(email_address)
916 self.background = background or self.str2color(email_address)
917 self.text_color = text_color
917 self.text_color = text_color
918
918
919 def get_color_bank(self):
919 def get_color_bank(self):
920 """
920 """
921 returns a predefined list of colors that gravatars can use.
921 returns a predefined list of colors that gravatars can use.
922 Those are randomized distinct colors that guarantee readability and
922 Those are randomized distinct colors that guarantee readability and
923 uniqueness.
923 uniqueness.
924
924
925 generated with: http://phrogz.net/css/distinct-colors.html
925 generated with: http://phrogz.net/css/distinct-colors.html
926 """
926 """
927 return [
927 return [
928 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
928 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
929 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
929 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
930 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
930 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
931 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
931 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
932 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
932 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
933 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
933 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
934 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
934 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
935 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
935 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
936 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
936 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
937 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
937 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
938 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
938 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
939 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
939 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
940 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
940 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
941 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
941 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
942 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
942 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
943 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
943 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
944 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
944 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
945 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
945 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
946 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
946 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
947 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
947 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
948 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
948 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
949 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
949 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
950 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
950 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
951 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
951 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
952 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
952 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
953 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
953 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
954 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
954 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
955 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
955 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
956 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
956 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
957 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
957 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
958 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
958 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
959 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
959 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
960 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
960 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
961 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
961 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
962 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
962 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
963 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
963 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
964 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
964 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
965 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
965 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
966 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
966 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
967 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
967 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
968 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
968 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
969 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
969 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
970 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
970 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
971 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
971 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
972 '#4f8c46', '#368dd9', '#5c0073'
972 '#4f8c46', '#368dd9', '#5c0073'
973 ]
973 ]
974
974
975 def rgb_to_hex_color(self, rgb_tuple):
975 def rgb_to_hex_color(self, rgb_tuple):
976 """
976 """
977 Converts an rgb_tuple passed to an hex color.
977 Converts an rgb_tuple passed to an hex color.
978
978
979 :param rgb_tuple: tuple with 3 ints represents rgb color space
979 :param rgb_tuple: tuple with 3 ints represents rgb color space
980 """
980 """
981 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
981 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
982
982
983 def email_to_int_list(self, email_str):
983 def email_to_int_list(self, email_str):
984 """
984 """
985 Get every byte of the hex digest value of email and turn it to integer.
985 Get every byte of the hex digest value of email and turn it to integer.
986 It's going to be always between 0-255
986 It's going to be always between 0-255
987 """
987 """
988 digest = md5_safe(email_str.lower())
988 digest = md5_safe(email_str.lower())
989 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
989 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
990
990
991 def pick_color_bank_index(self, email_str, color_bank):
991 def pick_color_bank_index(self, email_str, color_bank):
992 return self.email_to_int_list(email_str)[0] % len(color_bank)
992 return self.email_to_int_list(email_str)[0] % len(color_bank)
993
993
994 def str2color(self, email_str):
994 def str2color(self, email_str):
995 """
995 """
996 Tries to map in a stable algorithm an email to color
996 Tries to map in a stable algorithm an email to color
997
997
998 :param email_str:
998 :param email_str:
999 """
999 """
1000 color_bank = self.get_color_bank()
1000 color_bank = self.get_color_bank()
1001 # pick position (module it's length so we always find it in the
1001 # pick position (module it's length so we always find it in the
1002 # bank even if it's smaller than 256 values
1002 # bank even if it's smaller than 256 values
1003 pos = self.pick_color_bank_index(email_str, color_bank)
1003 pos = self.pick_color_bank_index(email_str, color_bank)
1004 return color_bank[pos]
1004 return color_bank[pos]
1005
1005
1006 def normalize_email(self, email_address):
1006 def normalize_email(self, email_address):
1007 import unicodedata
1007 import unicodedata
1008 # default host used to fill in the fake/missing email
1008 # default host used to fill in the fake/missing email
1009 default_host = u'localhost'
1009 default_host = u'localhost'
1010
1010
1011 if not email_address:
1011 if not email_address:
1012 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1012 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1013
1013
1014 email_address = safe_unicode(email_address)
1014 email_address = safe_unicode(email_address)
1015
1015
1016 if u'@' not in email_address:
1016 if u'@' not in email_address:
1017 email_address = u'%s@%s' % (email_address, default_host)
1017 email_address = u'%s@%s' % (email_address, default_host)
1018
1018
1019 if email_address.endswith(u'@'):
1019 if email_address.endswith(u'@'):
1020 email_address = u'%s%s' % (email_address, default_host)
1020 email_address = u'%s%s' % (email_address, default_host)
1021
1021
1022 email_address = unicodedata.normalize('NFKD', email_address)\
1022 email_address = unicodedata.normalize('NFKD', email_address)\
1023 .encode('ascii', 'ignore')
1023 .encode('ascii', 'ignore')
1024 return email_address
1024 return email_address
1025
1025
1026 def get_initials(self):
1026 def get_initials(self):
1027 """
1027 """
1028 Returns 2 letter initials calculated based on the input.
1028 Returns 2 letter initials calculated based on the input.
1029 The algorithm picks first given email address, and takes first letter
1029 The algorithm picks first given email address, and takes first letter
1030 of part before @, and then the first letter of server name. In case
1030 of part before @, and then the first letter of server name. In case
1031 the part before @ is in a format of `somestring.somestring2` it replaces
1031 the part before @ is in a format of `somestring.somestring2` it replaces
1032 the server letter with first letter of somestring2
1032 the server letter with first letter of somestring2
1033
1033
1034 In case function was initialized with both first and lastname, this
1034 In case function was initialized with both first and lastname, this
1035 overrides the extraction from email by first letter of the first and
1035 overrides the extraction from email by first letter of the first and
1036 last name. We add special logic to that functionality, In case Full name
1036 last name. We add special logic to that functionality, In case Full name
1037 is compound, like Guido Von Rossum, we use last part of the last name
1037 is compound, like Guido Von Rossum, we use last part of the last name
1038 (Von Rossum) picking `R`.
1038 (Von Rossum) picking `R`.
1039
1039
1040 Function also normalizes the non-ascii characters to they ascii
1040 Function also normalizes the non-ascii characters to they ascii
1041 representation, eg Δ„ => A
1041 representation, eg Δ„ => A
1042 """
1042 """
1043 import unicodedata
1043 import unicodedata
1044 # replace non-ascii to ascii
1044 # replace non-ascii to ascii
1045 first_name = unicodedata.normalize(
1045 first_name = unicodedata.normalize(
1046 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1046 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1047 last_name = unicodedata.normalize(
1047 last_name = unicodedata.normalize(
1048 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1048 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1049
1049
1050 # do NFKD encoding, and also make sure email has proper format
1050 # do NFKD encoding, and also make sure email has proper format
1051 email_address = self.normalize_email(self.email_address)
1051 email_address = self.normalize_email(self.email_address)
1052
1052
1053 # first push the email initials
1053 # first push the email initials
1054 prefix, server = email_address.split('@', 1)
1054 prefix, server = email_address.split('@', 1)
1055
1055
1056 # check if prefix is maybe a 'firstname.lastname' syntax
1056 # check if prefix is maybe a 'firstname.lastname' syntax
1057 _dot_split = prefix.rsplit('.', 1)
1057 _dot_split = prefix.rsplit('.', 1)
1058 if len(_dot_split) == 2:
1058 if len(_dot_split) == 2:
1059 initials = [_dot_split[0][0], _dot_split[1][0]]
1059 initials = [_dot_split[0][0], _dot_split[1][0]]
1060 else:
1060 else:
1061 initials = [prefix[0], server[0]]
1061 initials = [prefix[0], server[0]]
1062
1062
1063 # then try to replace either firtname or lastname
1063 # then try to replace either firtname or lastname
1064 fn_letter = (first_name or " ")[0].strip()
1064 fn_letter = (first_name or " ")[0].strip()
1065 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1065 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1066
1066
1067 if fn_letter:
1067 if fn_letter:
1068 initials[0] = fn_letter
1068 initials[0] = fn_letter
1069
1069
1070 if ln_letter:
1070 if ln_letter:
1071 initials[1] = ln_letter
1071 initials[1] = ln_letter
1072
1072
1073 return ''.join(initials).upper()
1073 return ''.join(initials).upper()
1074
1074
1075 def get_img_data_by_type(self, font_family, img_type):
1075 def get_img_data_by_type(self, font_family, img_type):
1076 default_user = """
1076 default_user = """
1077 <svg xmlns="http://www.w3.org/2000/svg"
1077 <svg xmlns="http://www.w3.org/2000/svg"
1078 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1078 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1079 viewBox="-15 -10 439.165 429.164"
1079 viewBox="-15 -10 439.165 429.164"
1080
1080
1081 xml:space="preserve"
1081 xml:space="preserve"
1082 style="background:{background};" >
1082 style="background:{background};" >
1083
1083
1084 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1084 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1085 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1085 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1086 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1086 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1087 168.596,153.916,216.671,
1087 168.596,153.916,216.671,
1088 204.583,216.671z" fill="{text_color}"/>
1088 204.583,216.671z" fill="{text_color}"/>
1089 <path d="M407.164,374.717L360.88,
1089 <path d="M407.164,374.717L360.88,
1090 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1090 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1091 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1091 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1092 15.366-44.203,23.488-69.076,23.488c-24.877,
1092 15.366-44.203,23.488-69.076,23.488c-24.877,
1093 0-48.762-8.122-69.078-23.488
1093 0-48.762-8.122-69.078-23.488
1094 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1094 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1095 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1095 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1096 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1096 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1097 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1097 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1098 19.402-10.527 C409.699,390.129,
1098 19.402-10.527 C409.699,390.129,
1099 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1099 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1100 </svg>""".format(
1100 </svg>""".format(
1101 size=self.size,
1101 size=self.size,
1102 background='#979797', # @grey4
1102 background='#979797', # @grey4
1103 text_color=self.text_color,
1103 text_color=self.text_color,
1104 font_family=font_family)
1104 font_family=font_family)
1105
1105
1106 return {
1106 return {
1107 "default_user": default_user
1107 "default_user": default_user
1108 }[img_type]
1108 }[img_type]
1109
1109
1110 def get_img_data(self, svg_type=None):
1110 def get_img_data(self, svg_type=None):
1111 """
1111 """
1112 generates the svg metadata for image
1112 generates the svg metadata for image
1113 """
1113 """
1114
1114
1115 font_family = ','.join([
1115 font_family = ','.join([
1116 'proximanovaregular',
1116 'proximanovaregular',
1117 'Proxima Nova Regular',
1117 'Proxima Nova Regular',
1118 'Proxima Nova',
1118 'Proxima Nova',
1119 'Arial',
1119 'Arial',
1120 'Lucida Grande',
1120 'Lucida Grande',
1121 'sans-serif'
1121 'sans-serif'
1122 ])
1122 ])
1123 if svg_type:
1123 if svg_type:
1124 return self.get_img_data_by_type(font_family, svg_type)
1124 return self.get_img_data_by_type(font_family, svg_type)
1125
1125
1126 initials = self.get_initials()
1126 initials = self.get_initials()
1127 img_data = """
1127 img_data = """
1128 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1128 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1129 width="{size}" height="{size}"
1129 width="{size}" height="{size}"
1130 style="width: 100%; height: 100%; background-color: {background}"
1130 style="width: 100%; height: 100%; background-color: {background}"
1131 viewBox="0 0 {size} {size}">
1131 viewBox="0 0 {size} {size}">
1132 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1132 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1133 pointer-events="auto" fill="{text_color}"
1133 pointer-events="auto" fill="{text_color}"
1134 font-family="{font_family}"
1134 font-family="{font_family}"
1135 style="font-weight: 400; font-size: {f_size}px;">{text}
1135 style="font-weight: 400; font-size: {f_size}px;">{text}
1136 </text>
1136 </text>
1137 </svg>""".format(
1137 </svg>""".format(
1138 size=self.size,
1138 size=self.size,
1139 f_size=self.size/1.85, # scale the text inside the box nicely
1139 f_size=self.size/1.85, # scale the text inside the box nicely
1140 background=self.background,
1140 background=self.background,
1141 text_color=self.text_color,
1141 text_color=self.text_color,
1142 text=initials.upper(),
1142 text=initials.upper(),
1143 font_family=font_family)
1143 font_family=font_family)
1144
1144
1145 return img_data
1145 return img_data
1146
1146
1147 def generate_svg(self, svg_type=None):
1147 def generate_svg(self, svg_type=None):
1148 img_data = self.get_img_data(svg_type)
1148 img_data = self.get_img_data(svg_type)
1149 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1149 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1150
1150
1151
1151
1152 def initials_gravatar(email_address, first_name, last_name, size=30):
1152 def initials_gravatar(email_address, first_name, last_name, size=30):
1153 svg_type = None
1153 svg_type = None
1154 if email_address == User.DEFAULT_USER_EMAIL:
1154 if email_address == User.DEFAULT_USER_EMAIL:
1155 svg_type = 'default_user'
1155 svg_type = 'default_user'
1156 klass = InitialsGravatar(email_address, first_name, last_name, size)
1156 klass = InitialsGravatar(email_address, first_name, last_name, size)
1157 return klass.generate_svg(svg_type=svg_type)
1157 return klass.generate_svg(svg_type=svg_type)
1158
1158
1159
1159
1160 def gravatar_url(email_address, size=30):
1160 def gravatar_url(email_address, size=30):
1161 # doh, we need to re-import those to mock it later
1161 # doh, we need to re-import those to mock it later
1162 from pylons import tmpl_context as c
1162 from pylons import tmpl_context as c
1163
1163
1164 _use_gravatar = c.visual.use_gravatar
1164 _use_gravatar = c.visual.use_gravatar
1165 _gravatar_url = c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL
1165 _gravatar_url = c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL
1166
1166
1167 email_address = email_address or User.DEFAULT_USER_EMAIL
1167 email_address = email_address or User.DEFAULT_USER_EMAIL
1168 if isinstance(email_address, unicode):
1168 if isinstance(email_address, unicode):
1169 # hashlib crashes on unicode items
1169 # hashlib crashes on unicode items
1170 email_address = safe_str(email_address)
1170 email_address = safe_str(email_address)
1171
1171
1172 # empty email or default user
1172 # empty email or default user
1173 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1173 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1174 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1174 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1175
1175
1176 if _use_gravatar:
1176 if _use_gravatar:
1177 # TODO: Disuse pyramid thread locals. Think about another solution to
1177 # TODO: Disuse pyramid thread locals. Think about another solution to
1178 # get the host and schema here.
1178 # get the host and schema here.
1179 request = get_current_request()
1179 request = get_current_request()
1180 tmpl = safe_str(_gravatar_url)
1180 tmpl = safe_str(_gravatar_url)
1181 tmpl = tmpl.replace('{email}', email_address)\
1181 tmpl = tmpl.replace('{email}', email_address)\
1182 .replace('{md5email}', md5_safe(email_address.lower())) \
1182 .replace('{md5email}', md5_safe(email_address.lower())) \
1183 .replace('{netloc}', request.host)\
1183 .replace('{netloc}', request.host)\
1184 .replace('{scheme}', request.scheme)\
1184 .replace('{scheme}', request.scheme)\
1185 .replace('{size}', safe_str(size))
1185 .replace('{size}', safe_str(size))
1186 return tmpl
1186 return tmpl
1187 else:
1187 else:
1188 return initials_gravatar(email_address, '', '', size=size)
1188 return initials_gravatar(email_address, '', '', size=size)
1189
1189
1190
1190
1191 class Page(_Page):
1191 class Page(_Page):
1192 """
1192 """
1193 Custom pager to match rendering style with paginator
1193 Custom pager to match rendering style with paginator
1194 """
1194 """
1195
1195
1196 def _get_pos(self, cur_page, max_page, items):
1196 def _get_pos(self, cur_page, max_page, items):
1197 edge = (items / 2) + 1
1197 edge = (items / 2) + 1
1198 if (cur_page <= edge):
1198 if (cur_page <= edge):
1199 radius = max(items / 2, items - cur_page)
1199 radius = max(items / 2, items - cur_page)
1200 elif (max_page - cur_page) < edge:
1200 elif (max_page - cur_page) < edge:
1201 radius = (items - 1) - (max_page - cur_page)
1201 radius = (items - 1) - (max_page - cur_page)
1202 else:
1202 else:
1203 radius = items / 2
1203 radius = items / 2
1204
1204
1205 left = max(1, (cur_page - (radius)))
1205 left = max(1, (cur_page - (radius)))
1206 right = min(max_page, cur_page + (radius))
1206 right = min(max_page, cur_page + (radius))
1207 return left, cur_page, right
1207 return left, cur_page, right
1208
1208
1209 def _range(self, regexp_match):
1209 def _range(self, regexp_match):
1210 """
1210 """
1211 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1211 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1212
1212
1213 Arguments:
1213 Arguments:
1214
1214
1215 regexp_match
1215 regexp_match
1216 A "re" (regular expressions) match object containing the
1216 A "re" (regular expressions) match object containing the
1217 radius of linked pages around the current page in
1217 radius of linked pages around the current page in
1218 regexp_match.group(1) as a string
1218 regexp_match.group(1) as a string
1219
1219
1220 This function is supposed to be called as a callable in
1220 This function is supposed to be called as a callable in
1221 re.sub.
1221 re.sub.
1222
1222
1223 """
1223 """
1224 radius = int(regexp_match.group(1))
1224 radius = int(regexp_match.group(1))
1225
1225
1226 # Compute the first and last page number within the radius
1226 # Compute the first and last page number within the radius
1227 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1227 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1228 # -> leftmost_page = 5
1228 # -> leftmost_page = 5
1229 # -> rightmost_page = 9
1229 # -> rightmost_page = 9
1230 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1230 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1231 self.last_page,
1231 self.last_page,
1232 (radius * 2) + 1)
1232 (radius * 2) + 1)
1233 nav_items = []
1233 nav_items = []
1234
1234
1235 # Create a link to the first page (unless we are on the first page
1235 # Create a link to the first page (unless we are on the first page
1236 # or there would be no need to insert '..' spacers)
1236 # or there would be no need to insert '..' spacers)
1237 if self.page != self.first_page and self.first_page < leftmost_page:
1237 if self.page != self.first_page and self.first_page < leftmost_page:
1238 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1238 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1239
1239
1240 # Insert dots if there are pages between the first page
1240 # Insert dots if there are pages between the first page
1241 # and the currently displayed page range
1241 # and the currently displayed page range
1242 if leftmost_page - self.first_page > 1:
1242 if leftmost_page - self.first_page > 1:
1243 # Wrap in a SPAN tag if nolink_attr is set
1243 # Wrap in a SPAN tag if nolink_attr is set
1244 text = '..'
1244 text = '..'
1245 if self.dotdot_attr:
1245 if self.dotdot_attr:
1246 text = HTML.span(c=text, **self.dotdot_attr)
1246 text = HTML.span(c=text, **self.dotdot_attr)
1247 nav_items.append(text)
1247 nav_items.append(text)
1248
1248
1249 for thispage in xrange(leftmost_page, rightmost_page + 1):
1249 for thispage in xrange(leftmost_page, rightmost_page + 1):
1250 # Hilight the current page number and do not use a link
1250 # Hilight the current page number and do not use a link
1251 if thispage == self.page:
1251 if thispage == self.page:
1252 text = '%s' % (thispage,)
1252 text = '%s' % (thispage,)
1253 # Wrap in a SPAN tag if nolink_attr is set
1253 # Wrap in a SPAN tag if nolink_attr is set
1254 if self.curpage_attr:
1254 if self.curpage_attr:
1255 text = HTML.span(c=text, **self.curpage_attr)
1255 text = HTML.span(c=text, **self.curpage_attr)
1256 nav_items.append(text)
1256 nav_items.append(text)
1257 # Otherwise create just a link to that page
1257 # Otherwise create just a link to that page
1258 else:
1258 else:
1259 text = '%s' % (thispage,)
1259 text = '%s' % (thispage,)
1260 nav_items.append(self._pagerlink(thispage, text))
1260 nav_items.append(self._pagerlink(thispage, text))
1261
1261
1262 # Insert dots if there are pages between the displayed
1262 # Insert dots if there are pages between the displayed
1263 # page numbers and the end of the page range
1263 # page numbers and the end of the page range
1264 if self.last_page - rightmost_page > 1:
1264 if self.last_page - rightmost_page > 1:
1265 text = '..'
1265 text = '..'
1266 # Wrap in a SPAN tag if nolink_attr is set
1266 # Wrap in a SPAN tag if nolink_attr is set
1267 if self.dotdot_attr:
1267 if self.dotdot_attr:
1268 text = HTML.span(c=text, **self.dotdot_attr)
1268 text = HTML.span(c=text, **self.dotdot_attr)
1269 nav_items.append(text)
1269 nav_items.append(text)
1270
1270
1271 # Create a link to the very last page (unless we are on the last
1271 # Create a link to the very last page (unless we are on the last
1272 # page or there would be no need to insert '..' spacers)
1272 # page or there would be no need to insert '..' spacers)
1273 if self.page != self.last_page and rightmost_page < self.last_page:
1273 if self.page != self.last_page and rightmost_page < self.last_page:
1274 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1274 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1275
1275
1276 ## prerender links
1276 ## prerender links
1277 #_page_link = url.current()
1277 #_page_link = url.current()
1278 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1278 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1279 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1279 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1280 return self.separator.join(nav_items)
1280 return self.separator.join(nav_items)
1281
1281
1282 def pager(self, format='~2~', page_param='page', partial_param='partial',
1282 def pager(self, format='~2~', page_param='page', partial_param='partial',
1283 show_if_single_page=False, separator=' ', onclick=None,
1283 show_if_single_page=False, separator=' ', onclick=None,
1284 symbol_first='<<', symbol_last='>>',
1284 symbol_first='<<', symbol_last='>>',
1285 symbol_previous='<', symbol_next='>',
1285 symbol_previous='<', symbol_next='>',
1286 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1286 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1287 curpage_attr={'class': 'pager_curpage'},
1287 curpage_attr={'class': 'pager_curpage'},
1288 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1288 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1289
1289
1290 self.curpage_attr = curpage_attr
1290 self.curpage_attr = curpage_attr
1291 self.separator = separator
1291 self.separator = separator
1292 self.pager_kwargs = kwargs
1292 self.pager_kwargs = kwargs
1293 self.page_param = page_param
1293 self.page_param = page_param
1294 self.partial_param = partial_param
1294 self.partial_param = partial_param
1295 self.onclick = onclick
1295 self.onclick = onclick
1296 self.link_attr = link_attr
1296 self.link_attr = link_attr
1297 self.dotdot_attr = dotdot_attr
1297 self.dotdot_attr = dotdot_attr
1298
1298
1299 # Don't show navigator if there is no more than one page
1299 # Don't show navigator if there is no more than one page
1300 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1300 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1301 return ''
1301 return ''
1302
1302
1303 from string import Template
1303 from string import Template
1304 # Replace ~...~ in token format by range of pages
1304 # Replace ~...~ in token format by range of pages
1305 result = re.sub(r'~(\d+)~', self._range, format)
1305 result = re.sub(r'~(\d+)~', self._range, format)
1306
1306
1307 # Interpolate '%' variables
1307 # Interpolate '%' variables
1308 result = Template(result).safe_substitute({
1308 result = Template(result).safe_substitute({
1309 'first_page': self.first_page,
1309 'first_page': self.first_page,
1310 'last_page': self.last_page,
1310 'last_page': self.last_page,
1311 'page': self.page,
1311 'page': self.page,
1312 'page_count': self.page_count,
1312 'page_count': self.page_count,
1313 'items_per_page': self.items_per_page,
1313 'items_per_page': self.items_per_page,
1314 'first_item': self.first_item,
1314 'first_item': self.first_item,
1315 'last_item': self.last_item,
1315 'last_item': self.last_item,
1316 'item_count': self.item_count,
1316 'item_count': self.item_count,
1317 'link_first': self.page > self.first_page and \
1317 'link_first': self.page > self.first_page and \
1318 self._pagerlink(self.first_page, symbol_first) or '',
1318 self._pagerlink(self.first_page, symbol_first) or '',
1319 'link_last': self.page < self.last_page and \
1319 'link_last': self.page < self.last_page and \
1320 self._pagerlink(self.last_page, symbol_last) or '',
1320 self._pagerlink(self.last_page, symbol_last) or '',
1321 'link_previous': self.previous_page and \
1321 'link_previous': self.previous_page and \
1322 self._pagerlink(self.previous_page, symbol_previous) \
1322 self._pagerlink(self.previous_page, symbol_previous) \
1323 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1323 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1324 'link_next': self.next_page and \
1324 'link_next': self.next_page and \
1325 self._pagerlink(self.next_page, symbol_next) \
1325 self._pagerlink(self.next_page, symbol_next) \
1326 or HTML.span(symbol_next, class_="pg-next disabled")
1326 or HTML.span(symbol_next, class_="pg-next disabled")
1327 })
1327 })
1328
1328
1329 return literal(result)
1329 return literal(result)
1330
1330
1331
1331
1332 #==============================================================================
1332 #==============================================================================
1333 # REPO PAGER, PAGER FOR REPOSITORY
1333 # REPO PAGER, PAGER FOR REPOSITORY
1334 #==============================================================================
1334 #==============================================================================
1335 class RepoPage(Page):
1335 class RepoPage(Page):
1336
1336
1337 def __init__(self, collection, page=1, items_per_page=20,
1337 def __init__(self, collection, page=1, items_per_page=20,
1338 item_count=None, url=None, **kwargs):
1338 item_count=None, url=None, **kwargs):
1339
1339
1340 """Create a "RepoPage" instance. special pager for paging
1340 """Create a "RepoPage" instance. special pager for paging
1341 repository
1341 repository
1342 """
1342 """
1343 self._url_generator = url
1343 self._url_generator = url
1344
1344
1345 # Safe the kwargs class-wide so they can be used in the pager() method
1345 # Safe the kwargs class-wide so they can be used in the pager() method
1346 self.kwargs = kwargs
1346 self.kwargs = kwargs
1347
1347
1348 # Save a reference to the collection
1348 # Save a reference to the collection
1349 self.original_collection = collection
1349 self.original_collection = collection
1350
1350
1351 self.collection = collection
1351 self.collection = collection
1352
1352
1353 # The self.page is the number of the current page.
1353 # The self.page is the number of the current page.
1354 # The first page has the number 1!
1354 # The first page has the number 1!
1355 try:
1355 try:
1356 self.page = int(page) # make it int() if we get it as a string
1356 self.page = int(page) # make it int() if we get it as a string
1357 except (ValueError, TypeError):
1357 except (ValueError, TypeError):
1358 self.page = 1
1358 self.page = 1
1359
1359
1360 self.items_per_page = items_per_page
1360 self.items_per_page = items_per_page
1361
1361
1362 # Unless the user tells us how many items the collections has
1362 # Unless the user tells us how many items the collections has
1363 # we calculate that ourselves.
1363 # we calculate that ourselves.
1364 if item_count is not None:
1364 if item_count is not None:
1365 self.item_count = item_count
1365 self.item_count = item_count
1366 else:
1366 else:
1367 self.item_count = len(self.collection)
1367 self.item_count = len(self.collection)
1368
1368
1369 # Compute the number of the first and last available page
1369 # Compute the number of the first and last available page
1370 if self.item_count > 0:
1370 if self.item_count > 0:
1371 self.first_page = 1
1371 self.first_page = 1
1372 self.page_count = int(math.ceil(float(self.item_count) /
1372 self.page_count = int(math.ceil(float(self.item_count) /
1373 self.items_per_page))
1373 self.items_per_page))
1374 self.last_page = self.first_page + self.page_count - 1
1374 self.last_page = self.first_page + self.page_count - 1
1375
1375
1376 # Make sure that the requested page number is the range of
1376 # Make sure that the requested page number is the range of
1377 # valid pages
1377 # valid pages
1378 if self.page > self.last_page:
1378 if self.page > self.last_page:
1379 self.page = self.last_page
1379 self.page = self.last_page
1380 elif self.page < self.first_page:
1380 elif self.page < self.first_page:
1381 self.page = self.first_page
1381 self.page = self.first_page
1382
1382
1383 # Note: the number of items on this page can be less than
1383 # Note: the number of items on this page can be less than
1384 # items_per_page if the last page is not full
1384 # items_per_page if the last page is not full
1385 self.first_item = max(0, (self.item_count) - (self.page *
1385 self.first_item = max(0, (self.item_count) - (self.page *
1386 items_per_page))
1386 items_per_page))
1387 self.last_item = ((self.item_count - 1) - items_per_page *
1387 self.last_item = ((self.item_count - 1) - items_per_page *
1388 (self.page - 1))
1388 (self.page - 1))
1389
1389
1390 self.items = list(self.collection[self.first_item:self.last_item + 1])
1390 self.items = list(self.collection[self.first_item:self.last_item + 1])
1391
1391
1392 # Links to previous and next page
1392 # Links to previous and next page
1393 if self.page > self.first_page:
1393 if self.page > self.first_page:
1394 self.previous_page = self.page - 1
1394 self.previous_page = self.page - 1
1395 else:
1395 else:
1396 self.previous_page = None
1396 self.previous_page = None
1397
1397
1398 if self.page < self.last_page:
1398 if self.page < self.last_page:
1399 self.next_page = self.page + 1
1399 self.next_page = self.page + 1
1400 else:
1400 else:
1401 self.next_page = None
1401 self.next_page = None
1402
1402
1403 # No items available
1403 # No items available
1404 else:
1404 else:
1405 self.first_page = None
1405 self.first_page = None
1406 self.page_count = 0
1406 self.page_count = 0
1407 self.last_page = None
1407 self.last_page = None
1408 self.first_item = None
1408 self.first_item = None
1409 self.last_item = None
1409 self.last_item = None
1410 self.previous_page = None
1410 self.previous_page = None
1411 self.next_page = None
1411 self.next_page = None
1412 self.items = []
1412 self.items = []
1413
1413
1414 # This is a subclass of the 'list' type. Initialise the list now.
1414 # This is a subclass of the 'list' type. Initialise the list now.
1415 list.__init__(self, reversed(self.items))
1415 list.__init__(self, reversed(self.items))
1416
1416
1417
1417
1418 def changed_tooltip(nodes):
1418 def changed_tooltip(nodes):
1419 """
1419 """
1420 Generates a html string for changed nodes in commit page.
1420 Generates a html string for changed nodes in commit page.
1421 It limits the output to 30 entries
1421 It limits the output to 30 entries
1422
1422
1423 :param nodes: LazyNodesGenerator
1423 :param nodes: LazyNodesGenerator
1424 """
1424 """
1425 if nodes:
1425 if nodes:
1426 pref = ': <br/> '
1426 pref = ': <br/> '
1427 suf = ''
1427 suf = ''
1428 if len(nodes) > 30:
1428 if len(nodes) > 30:
1429 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1429 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1430 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1430 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1431 for x in nodes[:30]]) + suf)
1431 for x in nodes[:30]]) + suf)
1432 else:
1432 else:
1433 return ': ' + _('No Files')
1433 return ': ' + _('No Files')
1434
1434
1435
1435
1436 def breadcrumb_repo_link(repo):
1436 def breadcrumb_repo_link(repo):
1437 """
1437 """
1438 Makes a breadcrumbs path link to repo
1438 Makes a breadcrumbs path link to repo
1439
1439
1440 ex::
1440 ex::
1441 group >> subgroup >> repo
1441 group >> subgroup >> repo
1442
1442
1443 :param repo: a Repository instance
1443 :param repo: a Repository instance
1444 """
1444 """
1445
1445
1446 path = [
1446 path = [
1447 link_to(group.name, url('repo_group_home', group_name=group.group_name))
1447 link_to(group.name, url('repo_group_home', group_name=group.group_name))
1448 for group in repo.groups_with_parents
1448 for group in repo.groups_with_parents
1449 ] + [
1449 ] + [
1450 link_to(repo.just_name, url('summary_home', repo_name=repo.repo_name))
1450 link_to(repo.just_name, url('summary_home', repo_name=repo.repo_name))
1451 ]
1451 ]
1452
1452
1453 return literal(' &raquo; '.join(path))
1453 return literal(' &raquo; '.join(path))
1454
1454
1455
1455
1456 def format_byte_size_binary(file_size):
1456 def format_byte_size_binary(file_size):
1457 """
1457 """
1458 Formats file/folder sizes to standard.
1458 Formats file/folder sizes to standard.
1459 """
1459 """
1460 formatted_size = format_byte_size(file_size, binary=True)
1460 formatted_size = format_byte_size(file_size, binary=True)
1461 return formatted_size
1461 return formatted_size
1462
1462
1463
1463
1464 def fancy_file_stats(stats):
1464 def fancy_file_stats(stats):
1465 """
1465 """
1466 Displays a fancy two colored bar for number of added/deleted
1466 Displays a fancy two colored bar for number of added/deleted
1467 lines of code on file
1467 lines of code on file
1468
1468
1469 :param stats: two element list of added/deleted lines of code
1469 :param stats: two element list of added/deleted lines of code
1470 """
1470 """
1471 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1471 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1472 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1472 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1473
1473
1474 def cgen(l_type, a_v, d_v):
1474 def cgen(l_type, a_v, d_v):
1475 mapping = {'tr': 'top-right-rounded-corner-mid',
1475 mapping = {'tr': 'top-right-rounded-corner-mid',
1476 'tl': 'top-left-rounded-corner-mid',
1476 'tl': 'top-left-rounded-corner-mid',
1477 'br': 'bottom-right-rounded-corner-mid',
1477 'br': 'bottom-right-rounded-corner-mid',
1478 'bl': 'bottom-left-rounded-corner-mid'}
1478 'bl': 'bottom-left-rounded-corner-mid'}
1479 map_getter = lambda x: mapping[x]
1479 map_getter = lambda x: mapping[x]
1480
1480
1481 if l_type == 'a' and d_v:
1481 if l_type == 'a' and d_v:
1482 #case when added and deleted are present
1482 #case when added and deleted are present
1483 return ' '.join(map(map_getter, ['tl', 'bl']))
1483 return ' '.join(map(map_getter, ['tl', 'bl']))
1484
1484
1485 if l_type == 'a' and not d_v:
1485 if l_type == 'a' and not d_v:
1486 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1486 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1487
1487
1488 if l_type == 'd' and a_v:
1488 if l_type == 'd' and a_v:
1489 return ' '.join(map(map_getter, ['tr', 'br']))
1489 return ' '.join(map(map_getter, ['tr', 'br']))
1490
1490
1491 if l_type == 'd' and not a_v:
1491 if l_type == 'd' and not a_v:
1492 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1492 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1493
1493
1494 a, d = stats['added'], stats['deleted']
1494 a, d = stats['added'], stats['deleted']
1495 width = 100
1495 width = 100
1496
1496
1497 if stats['binary']: # binary operations like chmod/rename etc
1497 if stats['binary']: # binary operations like chmod/rename etc
1498 lbl = []
1498 lbl = []
1499 bin_op = 0 # undefined
1499 bin_op = 0 # undefined
1500
1500
1501 # prefix with bin for binary files
1501 # prefix with bin for binary files
1502 if BIN_FILENODE in stats['ops']:
1502 if BIN_FILENODE in stats['ops']:
1503 lbl += ['bin']
1503 lbl += ['bin']
1504
1504
1505 if NEW_FILENODE in stats['ops']:
1505 if NEW_FILENODE in stats['ops']:
1506 lbl += [_('new file')]
1506 lbl += [_('new file')]
1507 bin_op = NEW_FILENODE
1507 bin_op = NEW_FILENODE
1508 elif MOD_FILENODE in stats['ops']:
1508 elif MOD_FILENODE in stats['ops']:
1509 lbl += [_('mod')]
1509 lbl += [_('mod')]
1510 bin_op = MOD_FILENODE
1510 bin_op = MOD_FILENODE
1511 elif DEL_FILENODE in stats['ops']:
1511 elif DEL_FILENODE in stats['ops']:
1512 lbl += [_('del')]
1512 lbl += [_('del')]
1513 bin_op = DEL_FILENODE
1513 bin_op = DEL_FILENODE
1514 elif RENAMED_FILENODE in stats['ops']:
1514 elif RENAMED_FILENODE in stats['ops']:
1515 lbl += [_('rename')]
1515 lbl += [_('rename')]
1516 bin_op = RENAMED_FILENODE
1516 bin_op = RENAMED_FILENODE
1517
1517
1518 # chmod can go with other operations, so we add a + to lbl if needed
1518 # chmod can go with other operations, so we add a + to lbl if needed
1519 if CHMOD_FILENODE in stats['ops']:
1519 if CHMOD_FILENODE in stats['ops']:
1520 lbl += [_('chmod')]
1520 lbl += [_('chmod')]
1521 if bin_op == 0:
1521 if bin_op == 0:
1522 bin_op = CHMOD_FILENODE
1522 bin_op = CHMOD_FILENODE
1523
1523
1524 lbl = '+'.join(lbl)
1524 lbl = '+'.join(lbl)
1525 b_a = '<div class="bin bin%s %s" style="width:100%%">%s</div>' \
1525 b_a = '<div class="bin bin%s %s" style="width:100%%">%s</div>' \
1526 % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1526 % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1527 b_d = '<div class="bin bin1" style="width:0%%"></div>'
1527 b_d = '<div class="bin bin1" style="width:0%%"></div>'
1528 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1528 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1529
1529
1530 t = stats['added'] + stats['deleted']
1530 t = stats['added'] + stats['deleted']
1531 unit = float(width) / (t or 1)
1531 unit = float(width) / (t or 1)
1532
1532
1533 # needs > 9% of width to be visible or 0 to be hidden
1533 # needs > 9% of width to be visible or 0 to be hidden
1534 a_p = max(9, unit * a) if a > 0 else 0
1534 a_p = max(9, unit * a) if a > 0 else 0
1535 d_p = max(9, unit * d) if d > 0 else 0
1535 d_p = max(9, unit * d) if d > 0 else 0
1536 p_sum = a_p + d_p
1536 p_sum = a_p + d_p
1537
1537
1538 if p_sum > width:
1538 if p_sum > width:
1539 #adjust the percentage to be == 100% since we adjusted to 9
1539 #adjust the percentage to be == 100% since we adjusted to 9
1540 if a_p > d_p:
1540 if a_p > d_p:
1541 a_p = a_p - (p_sum - width)
1541 a_p = a_p - (p_sum - width)
1542 else:
1542 else:
1543 d_p = d_p - (p_sum - width)
1543 d_p = d_p - (p_sum - width)
1544
1544
1545 a_v = a if a > 0 else ''
1545 a_v = a if a > 0 else ''
1546 d_v = d if d > 0 else ''
1546 d_v = d if d > 0 else ''
1547
1547
1548 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1548 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1549 cgen('a', a_v, d_v), a_p, a_v
1549 cgen('a', a_v, d_v), a_p, a_v
1550 )
1550 )
1551 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1551 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1552 cgen('d', a_v, d_v), d_p, d_v
1552 cgen('d', a_v, d_v), d_p, d_v
1553 )
1553 )
1554 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1554 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1555
1555
1556
1556
1557 def urlify_text(text_, safe=True):
1557 def urlify_text(text_, safe=True):
1558 """
1558 """
1559 Extrac urls from text and make html links out of them
1559 Extrac urls from text and make html links out of them
1560
1560
1561 :param text_:
1561 :param text_:
1562 """
1562 """
1563
1563
1564 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1564 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1565 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1565 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1566
1566
1567 def url_func(match_obj):
1567 def url_func(match_obj):
1568 url_full = match_obj.groups()[0]
1568 url_full = match_obj.groups()[0]
1569 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1569 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1570 _newtext = url_pat.sub(url_func, text_)
1570 _newtext = url_pat.sub(url_func, text_)
1571 if safe:
1571 if safe:
1572 return literal(_newtext)
1572 return literal(_newtext)
1573 return _newtext
1573 return _newtext
1574
1574
1575
1575
1576 def urlify_commits(text_, repository):
1576 def urlify_commits(text_, repository):
1577 """
1577 """
1578 Extract commit ids from text and make link from them
1578 Extract commit ids from text and make link from them
1579
1579
1580 :param text_:
1580 :param text_:
1581 :param repository: repo name to build the URL with
1581 :param repository: repo name to build the URL with
1582 """
1582 """
1583 from pylons import url # doh, we need to re-import url to mock it later
1583 from pylons import url # doh, we need to re-import url to mock it later
1584 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1584 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1585
1585
1586 def url_func(match_obj):
1586 def url_func(match_obj):
1587 commit_id = match_obj.groups()[1]
1587 commit_id = match_obj.groups()[1]
1588 pref = match_obj.groups()[0]
1588 pref = match_obj.groups()[0]
1589 suf = match_obj.groups()[2]
1589 suf = match_obj.groups()[2]
1590
1590
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 '%(commit_id)s</a>%(suf)s'
1593 '%(commit_id)s</a>%(suf)s'
1594 )
1594 )
1595 return tmpl % {
1595 return tmpl % {
1596 'pref': pref,
1596 'pref': pref,
1597 'cls': 'revision-link',
1597 'cls': 'revision-link',
1598 'url': url('changeset_home', repo_name=repository,
1598 'url': url('changeset_home', repo_name=repository,
1599 revision=commit_id),
1599 revision=commit_id),
1600 'commit_id': commit_id,
1600 'commit_id': commit_id,
1601 'suf': suf
1601 'suf': suf
1602 }
1602 }
1603
1603
1604 newtext = URL_PAT.sub(url_func, text_)
1604 newtext = URL_PAT.sub(url_func, text_)
1605
1605
1606 return newtext
1606 return newtext
1607
1607
1608
1608
1609 def _process_url_func(match_obj, repo_name, uid, entry):
1609 def _process_url_func(match_obj, repo_name, uid, entry):
1610 pref = ''
1610 pref = ''
1611 if match_obj.group().startswith(' '):
1611 if match_obj.group().startswith(' '):
1612 pref = ' '
1612 pref = ' '
1613
1613
1614 issue_id = ''.join(match_obj.groups())
1614 issue_id = ''.join(match_obj.groups())
1615 tmpl = (
1615 tmpl = (
1616 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1616 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1617 '%(issue-prefix)s%(id-repr)s'
1617 '%(issue-prefix)s%(id-repr)s'
1618 '</a>')
1618 '</a>')
1619
1619
1620 (repo_name_cleaned,
1620 (repo_name_cleaned,
1621 parent_group_name) = RepoGroupModel().\
1621 parent_group_name) = RepoGroupModel().\
1622 _get_group_name_and_parent(repo_name)
1622 _get_group_name_and_parent(repo_name)
1623
1623
1624 # variables replacement
1624 # variables replacement
1625 named_vars = {
1625 named_vars = {
1626 'id': issue_id,
1626 'id': issue_id,
1627 'repo': repo_name,
1627 'repo': repo_name,
1628 'repo_name': repo_name_cleaned,
1628 'repo_name': repo_name_cleaned,
1629 'group_name': parent_group_name
1629 'group_name': parent_group_name
1630 }
1630 }
1631 # named regex variables
1631 # named regex variables
1632 named_vars.update(match_obj.groupdict())
1632 named_vars.update(match_obj.groupdict())
1633 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1633 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1634
1634
1635 return tmpl % {
1635 return tmpl % {
1636 'pref': pref,
1636 'pref': pref,
1637 'cls': 'issue-tracker-link',
1637 'cls': 'issue-tracker-link',
1638 'url': _url,
1638 'url': _url,
1639 'id-repr': issue_id,
1639 'id-repr': issue_id,
1640 'issue-prefix': entry['pref'],
1640 'issue-prefix': entry['pref'],
1641 'serv': entry['url'],
1641 'serv': entry['url'],
1642 }
1642 }
1643
1643
1644
1644
1645 def process_patterns(text_string, repo_name, config):
1645 def process_patterns(text_string, repo_name, config):
1646 repo = None
1646 repo = None
1647 if repo_name:
1647 if repo_name:
1648 # Retrieving repo_name to avoid invalid repo_name to explode on
1648 # Retrieving repo_name to avoid invalid repo_name to explode on
1649 # IssueTrackerSettingsModel but still passing invalid name further down
1649 # IssueTrackerSettingsModel but still passing invalid name further down
1650 repo = Repository.get_by_repo_name(repo_name)
1650 repo = Repository.get_by_repo_name(repo_name)
1651
1651
1652 settings_model = IssueTrackerSettingsModel(repo=repo)
1652 settings_model = IssueTrackerSettingsModel(repo=repo)
1653 active_entries = settings_model.get_settings()
1653 active_entries = settings_model.get_settings()
1654
1654
1655 newtext = text_string
1655 newtext = text_string
1656 for uid, entry in active_entries.items():
1656 for uid, entry in active_entries.items():
1657 url_func = partial(
1657 url_func = partial(
1658 _process_url_func, repo_name=repo_name, entry=entry, uid=uid)
1658 _process_url_func, repo_name=repo_name, entry=entry, uid=uid)
1659
1659
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 newtext = pattern.sub(url_func, newtext)
1677 newtext = pattern.sub(url_func, newtext)
1678 log.debug('processed prefix:uid `%s`' % (uid,))
1678 log.debug('processed prefix:uid `%s`' % (uid,))
1679
1679
1680 return newtext
1680 return newtext
1681
1681
1682
1682
1683 def urlify_commit_message(commit_text, repository=None):
1683 def urlify_commit_message(commit_text, repository=None):
1684 """
1684 """
1685 Parses given text message and makes proper links.
1685 Parses given text message and makes proper links.
1686 issues are linked to given issue-server, and rest is a commit link
1686 issues are linked to given issue-server, and rest is a commit link
1687
1687
1688 :param commit_text:
1688 :param commit_text:
1689 :param repository:
1689 :param repository:
1690 """
1690 """
1691 from pylons import url # doh, we need to re-import url to mock it later
1691 from pylons import url # doh, we need to re-import url to mock it later
1692 from rhodecode import CONFIG
1692 from rhodecode import CONFIG
1693
1693
1694 def escaper(string):
1694 def escaper(string):
1695 return string.replace('<', '&lt;').replace('>', '&gt;')
1695 return string.replace('<', '&lt;').replace('>', '&gt;')
1696
1696
1697 newtext = escaper(commit_text)
1697 newtext = escaper(commit_text)
1698 # urlify commits - extract commit ids and make link out of them, if we have
1698 # urlify commits - extract commit ids and make link out of them, if we have
1699 # the scope of repository present.
1699 # the scope of repository present.
1700 if repository:
1700 if repository:
1701 newtext = urlify_commits(newtext, repository)
1701 newtext = urlify_commits(newtext, repository)
1702
1702
1703 # extract http/https links and make them real urls
1703 # extract http/https links and make them real urls
1704 newtext = urlify_text(newtext, safe=False)
1704 newtext = urlify_text(newtext, safe=False)
1705
1705
1706 # process issue tracker patterns
1706 # process issue tracker patterns
1707 newtext = process_patterns(newtext, repository or '', CONFIG)
1707 newtext = process_patterns(newtext, repository or '', CONFIG)
1708
1708
1709 return literal(newtext)
1709 return literal(newtext)
1710
1710
1711
1711
1712 def rst(source, mentions=False):
1712 def rst(source, mentions=False):
1713 return literal('<div class="rst-block">%s</div>' %
1713 return literal('<div class="rst-block">%s</div>' %
1714 MarkupRenderer.rst(source, mentions=mentions))
1714 MarkupRenderer.rst(source, mentions=mentions))
1715
1715
1716
1716
1717 def markdown(source, mentions=False):
1717 def markdown(source, mentions=False):
1718 return literal('<div class="markdown-block">%s</div>' %
1718 return literal('<div class="markdown-block">%s</div>' %
1719 MarkupRenderer.markdown(source, flavored=False,
1719 MarkupRenderer.markdown(source, flavored=False,
1720 mentions=mentions))
1720 mentions=mentions))
1721
1721
1722 def renderer_from_filename(filename, exclude=None):
1722 def renderer_from_filename(filename, exclude=None):
1723 from rhodecode.config.conf import MARKDOWN_EXTS, RST_EXTS
1723 from rhodecode.config.conf import MARKDOWN_EXTS, RST_EXTS
1724
1724
1725 def _filter(elements):
1725 def _filter(elements):
1726 if isinstance(exclude, (list, tuple)):
1726 if isinstance(exclude, (list, tuple)):
1727 return [x for x in elements if x not in exclude]
1727 return [x for x in elements if x not in exclude]
1728 return elements
1728 return elements
1729
1729
1730 if filename.endswith(tuple(_filter([x[0] for x in MARKDOWN_EXTS if x[0]]))):
1730 if filename.endswith(tuple(_filter([x[0] for x in MARKDOWN_EXTS if x[0]]))):
1731 return 'markdown'
1731 return 'markdown'
1732 if filename.endswith(tuple(_filter([x[0] for x in RST_EXTS if x[0]]))):
1732 if filename.endswith(tuple(_filter([x[0] for x in RST_EXTS if x[0]]))):
1733 return 'rst'
1733 return 'rst'
1734
1734
1735
1735
1736 def render(source, renderer='rst', mentions=False):
1736 def render(source, renderer='rst', mentions=False):
1737 if renderer == 'rst':
1737 if renderer == 'rst':
1738 return rst(source, mentions=mentions)
1738 return rst(source, mentions=mentions)
1739 if renderer == 'markdown':
1739 if renderer == 'markdown':
1740 return markdown(source, mentions=mentions)
1740 return markdown(source, mentions=mentions)
1741
1741
1742
1742
1743 def commit_status(repo, commit_id):
1743 def commit_status(repo, commit_id):
1744 return ChangesetStatusModel().get_status(repo, commit_id)
1744 return ChangesetStatusModel().get_status(repo, commit_id)
1745
1745
1746
1746
1747 def commit_status_lbl(commit_status):
1747 def commit_status_lbl(commit_status):
1748 return dict(ChangesetStatus.STATUSES).get(commit_status)
1748 return dict(ChangesetStatus.STATUSES).get(commit_status)
1749
1749
1750
1750
1751 def commit_time(repo_name, commit_id):
1751 def commit_time(repo_name, commit_id):
1752 repo = Repository.get_by_repo_name(repo_name)
1752 repo = Repository.get_by_repo_name(repo_name)
1753 commit = repo.get_commit(commit_id=commit_id)
1753 commit = repo.get_commit(commit_id=commit_id)
1754 return commit.date
1754 return commit.date
1755
1755
1756
1756
1757 def get_permission_name(key):
1757 def get_permission_name(key):
1758 return dict(Permission.PERMS).get(key)
1758 return dict(Permission.PERMS).get(key)
1759
1759
1760
1760
1761 def journal_filter_help():
1761 def journal_filter_help():
1762 return _(
1762 return _(
1763 'Example filter terms:\n' +
1763 'Example filter terms:\n' +
1764 ' repository:vcs\n' +
1764 ' repository:vcs\n' +
1765 ' username:marcin\n' +
1765 ' username:marcin\n' +
1766 ' action:*push*\n' +
1766 ' action:*push*\n' +
1767 ' ip:127.0.0.1\n' +
1767 ' ip:127.0.0.1\n' +
1768 ' date:20120101\n' +
1768 ' date:20120101\n' +
1769 ' date:[20120101100000 TO 20120102]\n' +
1769 ' date:[20120101100000 TO 20120102]\n' +
1770 '\n' +
1770 '\n' +
1771 'Generate wildcards using \'*\' character:\n' +
1771 'Generate wildcards using \'*\' character:\n' +
1772 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1772 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1773 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1773 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1774 '\n' +
1774 '\n' +
1775 'Optional AND / OR operators in queries\n' +
1775 'Optional AND / OR operators in queries\n' +
1776 ' "repository:vcs OR repository:test"\n' +
1776 ' "repository:vcs OR repository:test"\n' +
1777 ' "username:test AND repository:test*"\n'
1777 ' "username:test AND repository:test*"\n'
1778 )
1778 )
1779
1779
1780
1780
1781 def not_mapped_error(repo_name):
1781 def not_mapped_error(repo_name):
1782 flash(_('%s repository is not mapped to db perhaps'
1782 flash(_('%s repository is not mapped to db perhaps'
1783 ' it was created or renamed from the filesystem'
1783 ' it was created or renamed from the filesystem'
1784 ' please run the application again'
1784 ' please run the application again'
1785 ' in order to rescan repositories') % repo_name, category='error')
1785 ' in order to rescan repositories') % repo_name, category='error')
1786
1786
1787
1787
1788 def ip_range(ip_addr):
1788 def ip_range(ip_addr):
1789 from rhodecode.model.db import UserIpMap
1789 from rhodecode.model.db import UserIpMap
1790 s, e = UserIpMap._get_ip_range(ip_addr)
1790 s, e = UserIpMap._get_ip_range(ip_addr)
1791 return '%s - %s' % (s, e)
1791 return '%s - %s' % (s, e)
1792
1792
1793
1793
1794 def form(url, method='post', needs_csrf_token=True, **attrs):
1794 def form(url, method='post', needs_csrf_token=True, **attrs):
1795 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1795 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1796 if method.lower() != 'get' and needs_csrf_token:
1796 if method.lower() != 'get' and needs_csrf_token:
1797 raise Exception(
1797 raise Exception(
1798 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1798 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1799 'CSRF token. If the endpoint does not require such token you can ' +
1799 'CSRF token. If the endpoint does not require such token you can ' +
1800 'explicitly set the parameter needs_csrf_token to false.')
1800 'explicitly set the parameter needs_csrf_token to false.')
1801
1801
1802 return wh_form(url, method=method, **attrs)
1802 return wh_form(url, method=method, **attrs)
1803
1803
1804
1804
1805 def secure_form(url, method="POST", multipart=False, **attrs):
1805 def secure_form(url, method="POST", multipart=False, **attrs):
1806 """Start a form tag that points the action to an url. This
1806 """Start a form tag that points the action to an url. This
1807 form tag will also include the hidden field containing
1807 form tag will also include the hidden field containing
1808 the auth token.
1808 the auth token.
1809
1809
1810 The url options should be given either as a string, or as a
1810 The url options should be given either as a string, or as a
1811 ``url()`` function. The method for the form defaults to POST.
1811 ``url()`` function. The method for the form defaults to POST.
1812
1812
1813 Options:
1813 Options:
1814
1814
1815 ``multipart``
1815 ``multipart``
1816 If set to True, the enctype is set to "multipart/form-data".
1816 If set to True, the enctype is set to "multipart/form-data".
1817 ``method``
1817 ``method``
1818 The method to use when submitting the form, usually either
1818 The method to use when submitting the form, usually either
1819 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1819 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1820 hidden input with name _method is added to simulate the verb
1820 hidden input with name _method is added to simulate the verb
1821 over POST.
1821 over POST.
1822
1822
1823 """
1823 """
1824 from webhelpers.pylonslib.secure_form import insecure_form
1824 from webhelpers.pylonslib.secure_form import insecure_form
1825 from rhodecode.lib.auth import get_csrf_token, csrf_token_key
1825 from rhodecode.lib.auth import get_csrf_token, csrf_token_key
1826 form = insecure_form(url, method, multipart, **attrs)
1826 form = insecure_form(url, method, multipart, **attrs)
1827 token = HTML.div(hidden(csrf_token_key, get_csrf_token()), style="display: none;")
1827 token = HTML.div(hidden(csrf_token_key, get_csrf_token()), style="display: none;")
1828 return literal("%s\n%s" % (form, token))
1828 return literal("%s\n%s" % (form, token))
1829
1829
1830 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1830 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1831 select_html = select(name, selected, options, **attrs)
1831 select_html = select(name, selected, options, **attrs)
1832 select2 = """
1832 select2 = """
1833 <script>
1833 <script>
1834 $(document).ready(function() {
1834 $(document).ready(function() {
1835 $('#%s').select2({
1835 $('#%s').select2({
1836 containerCssClass: 'drop-menu',
1836 containerCssClass: 'drop-menu',
1837 dropdownCssClass: 'drop-menu-dropdown',
1837 dropdownCssClass: 'drop-menu-dropdown',
1838 dropdownAutoWidth: true%s
1838 dropdownAutoWidth: true%s
1839 });
1839 });
1840 });
1840 });
1841 </script>
1841 </script>
1842 """
1842 """
1843 filter_option = """,
1843 filter_option = """,
1844 minimumResultsForSearch: -1
1844 minimumResultsForSearch: -1
1845 """
1845 """
1846 input_id = attrs.get('id') or name
1846 input_id = attrs.get('id') or name
1847 filter_enabled = "" if enable_filter else filter_option
1847 filter_enabled = "" if enable_filter else filter_option
1848 select_script = literal(select2 % (input_id, filter_enabled))
1848 select_script = literal(select2 % (input_id, filter_enabled))
1849
1849
1850 return literal(select_html+select_script)
1850 return literal(select_html+select_script)
1851
1851
1852
1852
1853 def get_visual_attr(tmpl_context_var, attr_name):
1853 def get_visual_attr(tmpl_context_var, attr_name):
1854 """
1854 """
1855 A safe way to get a variable from visual variable of template context
1855 A safe way to get a variable from visual variable of template context
1856
1856
1857 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1857 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1858 :param attr_name: name of the attribute we fetch from the c.visual
1858 :param attr_name: name of the attribute we fetch from the c.visual
1859 """
1859 """
1860 visual = getattr(tmpl_context_var, 'visual', None)
1860 visual = getattr(tmpl_context_var, 'visual', None)
1861 if not visual:
1861 if not visual:
1862 return
1862 return
1863 else:
1863 else:
1864 return getattr(visual, attr_name, None)
1864 return getattr(visual, attr_name, None)
1865
1865
1866
1866
1867 def get_last_path_part(file_node):
1867 def get_last_path_part(file_node):
1868 if not file_node.path:
1868 if not file_node.path:
1869 return u''
1869 return u''
1870
1870
1871 path = safe_unicode(file_node.path.split('/')[-1])
1871 path = safe_unicode(file_node.path.split('/')[-1])
1872 return u'../' + path
1872 return u'../' + path
1873
1873
1874
1874
1875 def route_path(*args, **kwds):
1875 def route_path(*args, **kwds):
1876 """
1876 """
1877 Wrapper around pyramids `route_path` function. It is used to generate
1877 Wrapper around pyramids `route_path` function. It is used to generate
1878 URLs from within pylons views or templates. This will be removed when
1878 URLs from within pylons views or templates. This will be removed when
1879 pyramid migration if finished.
1879 pyramid migration if finished.
1880 """
1880 """
1881 req = get_current_request()
1881 req = get_current_request()
1882 return req.route_path(*args, **kwds)
1882 return req.route_path(*args, **kwds)
1883
1883
1884
1884
1885 def resource_path(*args, **kwds):
1885 def resource_path(*args, **kwds):
1886 """
1886 """
1887 Wrapper around pyramids `route_path` function. It is used to generate
1887 Wrapper around pyramids `route_path` function. It is used to generate
1888 URLs from within pylons views or templates. This will be removed when
1888 URLs from within pylons views or templates. This will be removed when
1889 pyramid migration if finished.
1889 pyramid migration if finished.
1890 """
1890 """
1891 req = get_current_request()
1891 req = get_current_request()
1892 return req.resource_path(*args, **kwds)
1892 return req.resource_path(*args, **kwds)
@@ -1,655 +1,655 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="root.html"/>
2 <%inherit file="root.html"/>
3
3
4 <div class="outerwrapper">
4 <div class="outerwrapper">
5 <!-- HEADER -->
5 <!-- HEADER -->
6 <div class="header">
6 <div class="header">
7 <div id="header-inner" class="wrapper">
7 <div id="header-inner" class="wrapper">
8 <div id="logo">
8 <div id="logo">
9 <div class="logo-wrapper">
9 <div class="logo-wrapper">
10 <a href="${h.url('home')}"><img src="${h.url('/images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
10 <a href="${h.url('home')}"><img src="${h.url('/images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 </div>
11 </div>
12 %if c.rhodecode_name:
12 %if c.rhodecode_name:
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 %endif
14 %endif
15 </div>
15 </div>
16 <!-- MENU BAR NAV -->
16 <!-- MENU BAR NAV -->
17 ${self.menu_bar_nav()}
17 ${self.menu_bar_nav()}
18 <!-- END MENU BAR NAV -->
18 <!-- END MENU BAR NAV -->
19 ${self.body()}
19 ${self.body()}
20 </div>
20 </div>
21 </div>
21 </div>
22 ${self.menu_bar_subnav()}
22 ${self.menu_bar_subnav()}
23 <!-- END HEADER -->
23 <!-- END HEADER -->
24
24
25 <!-- CONTENT -->
25 <!-- CONTENT -->
26 <div id="content" class="wrapper">
26 <div id="content" class="wrapper">
27 ${self.flash_msg()}
27 ${self.flash_msg()}
28 <div class="main">
28 <div class="main">
29 ${next.main()}
29 ${next.main()}
30 </div>
30 </div>
31 </div>
31 </div>
32 <!-- END CONTENT -->
32 <!-- END CONTENT -->
33
33
34 </div>
34 </div>
35 <!-- FOOTER -->
35 <!-- FOOTER -->
36 <div id="footer">
36 <div id="footer">
37 <div id="footer-inner" class="title wrapper">
37 <div id="footer-inner" class="title wrapper">
38 <div>
38 <div>
39 <p class="footer-link-right">
39 <p class="footer-link-right">
40 % if c.visual.show_version:
40 % if c.visual.show_version:
41 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
41 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
42 % endif
42 % endif
43 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
43 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
44 % if c.visual.rhodecode_support_url:
44 % if c.visual.rhodecode_support_url:
45 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
45 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
46 % endif
46 % endif
47 </p>
47 </p>
48 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
48 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
49 <p class="server-instance" style="display:${sid}">
49 <p class="server-instance" style="display:${sid}">
50 ## display hidden instance ID if specially defined
50 ## display hidden instance ID if specially defined
51 % if c.rhodecode_instanceid:
51 % if c.rhodecode_instanceid:
52 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
52 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
53 % endif
53 % endif
54 </p>
54 </p>
55 </div>
55 </div>
56 </div>
56 </div>
57 </div>
57 </div>
58
58
59 <!-- END FOOTER -->
59 <!-- END FOOTER -->
60
60
61 ### MAKO DEFS ###
61 ### MAKO DEFS ###
62
62
63 <%def name="menu_bar_subnav()">
63 <%def name="menu_bar_subnav()">
64 </%def>
64 </%def>
65
65
66 <%def name="flash_msg()">
66 <%def name="flash_msg()">
67 <%include file="/base/flash_msg.html"/>
67 <%include file="/base/flash_msg.html"/>
68 </%def>
68 </%def>
69
69
70 <%def name="breadcrumbs(class_='breadcrumbs')">
70 <%def name="breadcrumbs(class_='breadcrumbs')">
71 <div class="${class_}">
71 <div class="${class_}">
72 ${self.breadcrumbs_links()}
72 ${self.breadcrumbs_links()}
73 </div>
73 </div>
74 </%def>
74 </%def>
75
75
76 <%def name="admin_menu()">
76 <%def name="admin_menu()">
77 <ul class="admin_menu submenu">
77 <ul class="admin_menu submenu">
78 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
78 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
79 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
79 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
80 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
80 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
81 <li><a href="${h.url('users')}">${_('Users')}</a></li>
81 <li><a href="${h.url('users')}">${_('Users')}</a></li>
82 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
82 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
83 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
83 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
84 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
84 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
85 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
85 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
86 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
86 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
87 </ul>
87 </ul>
88 </%def>
88 </%def>
89
89
90
90
91 <%def name="dt_info_panel(elements)">
91 <%def name="dt_info_panel(elements)">
92 <dl class="dl-horizontal">
92 <dl class="dl-horizontal">
93 %for dt, dd, title, show_items in elements:
93 %for dt, dd, title, show_items in elements:
94 <dt>${dt}:</dt>
94 <dt>${dt}:</dt>
95 <dd title="${title}">
95 <dd title="${title}">
96 %if callable(dd):
96 %if callable(dd):
97 ## allow lazy evaluation of elements
97 ## allow lazy evaluation of elements
98 ${dd()}
98 ${dd()}
99 %else:
99 %else:
100 ${dd}
100 ${dd}
101 %endif
101 %endif
102 %if show_items:
102 %if show_items:
103 <span class="btn-collapse" data-toggle="item-${h.md5(dt)[:6]}-details">${_('Show More')} </span>
103 <span class="btn-collapse" data-toggle="item-${h.md5(dt)[:6]}-details">${_('Show More')} </span>
104 %endif
104 %endif
105 </dd>
105 </dd>
106
106
107 %if show_items:
107 %if show_items:
108 <div class="collapsable-content" data-toggle="item-${h.md5(dt)[:6]}-details" style="display: none">
108 <div class="collapsable-content" data-toggle="item-${h.md5(dt)[:6]}-details" style="display: none">
109 %for item in show_items:
109 %for item in show_items:
110 <dt></dt>
110 <dt></dt>
111 <dd>${item}</dd>
111 <dd>${item}</dd>
112 %endfor
112 %endfor
113 </div>
113 </div>
114 %endif
114 %endif
115
115
116 %endfor
116 %endfor
117 </dl>
117 </dl>
118 </%def>
118 </%def>
119
119
120
120
121 <%def name="gravatar(email, size=16)">
121 <%def name="gravatar(email, size=16)">
122 <%
122 <%
123 if (size > 16):
123 if (size > 16):
124 gravatar_class = 'gravatar gravatar-large'
124 gravatar_class = 'gravatar gravatar-large'
125 else:
125 else:
126 gravatar_class = 'gravatar'
126 gravatar_class = 'gravatar'
127 %>
127 %>
128 <%doc>
128 <%doc>
129 TODO: johbo: For now we serve double size images to make it smooth
129 TODO: johbo: For now we serve double size images to make it smooth
130 for retina. This is how it worked until now. Should be replaced
130 for retina. This is how it worked until now. Should be replaced
131 with a better solution at some point.
131 with a better solution at some point.
132 </%doc>
132 </%doc>
133 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
133 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
134 </%def>
134 </%def>
135
135
136
136
137 <%def name="gravatar_with_user(contact, size=16)">
137 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
138 <div class="rc-user tooltip" title="${contact}">
138 <div class="rc-user tooltip" title="${contact}">
139 ${self.gravatar(h.email_or_none(contact), size)}
139 ${self.gravatar(h.email_or_none(contact), size)}
140 <span class="user"> ${h.link_to_user(contact)}</span>
140 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
141 </div>
141 </div>
142 </%def>
142 </%def>
143
143
144
144
145 ## admin menu used for people that have some admin resources
145 ## admin menu used for people that have some admin resources
146 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
146 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
147 <ul class="submenu">
147 <ul class="submenu">
148 %if repositories:
148 %if repositories:
149 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
149 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
150 %endif
150 %endif
151 %if repository_groups:
151 %if repository_groups:
152 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
152 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
153 %endif
153 %endif
154 %if user_groups:
154 %if user_groups:
155 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
155 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
156 %endif
156 %endif
157 </ul>
157 </ul>
158 </%def>
158 </%def>
159
159
160 <%def name="repo_page_title(repo_instance)">
160 <%def name="repo_page_title(repo_instance)">
161 <div class="title-content">
161 <div class="title-content">
162 <div class="title-main">
162 <div class="title-main">
163 ## SVN/HG/GIT icons
163 ## SVN/HG/GIT icons
164 %if h.is_hg(repo_instance):
164 %if h.is_hg(repo_instance):
165 <i class="icon-hg"></i>
165 <i class="icon-hg"></i>
166 %endif
166 %endif
167 %if h.is_git(repo_instance):
167 %if h.is_git(repo_instance):
168 <i class="icon-git"></i>
168 <i class="icon-git"></i>
169 %endif
169 %endif
170 %if h.is_svn(repo_instance):
170 %if h.is_svn(repo_instance):
171 <i class="icon-svn"></i>
171 <i class="icon-svn"></i>
172 %endif
172 %endif
173
173
174 ## public/private
174 ## public/private
175 %if repo_instance.private:
175 %if repo_instance.private:
176 <i class="icon-repo-private"></i>
176 <i class="icon-repo-private"></i>
177 %else:
177 %else:
178 <i class="icon-repo-public"></i>
178 <i class="icon-repo-public"></i>
179 %endif
179 %endif
180
180
181 ## repo name with group name
181 ## repo name with group name
182 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
182 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
183
183
184 </div>
184 </div>
185
185
186 ## FORKED
186 ## FORKED
187 %if repo_instance.fork:
187 %if repo_instance.fork:
188 <p>
188 <p>
189 <i class="icon-code-fork"></i> ${_('Fork of')}
189 <i class="icon-code-fork"></i> ${_('Fork of')}
190 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
190 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
191 </p>
191 </p>
192 %endif
192 %endif
193
193
194 ## IMPORTED FROM REMOTE
194 ## IMPORTED FROM REMOTE
195 %if repo_instance.clone_uri:
195 %if repo_instance.clone_uri:
196 <p>
196 <p>
197 <i class="icon-code-fork"></i> ${_('Clone from')}
197 <i class="icon-code-fork"></i> ${_('Clone from')}
198 <a href="${h.url(str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
198 <a href="${h.url(str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
199 </p>
199 </p>
200 %endif
200 %endif
201
201
202 ## LOCKING STATUS
202 ## LOCKING STATUS
203 %if repo_instance.locked[0]:
203 %if repo_instance.locked[0]:
204 <p class="locking_locked">
204 <p class="locking_locked">
205 <i class="icon-repo-lock"></i>
205 <i class="icon-repo-lock"></i>
206 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
206 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
207 </p>
207 </p>
208 %elif repo_instance.enable_locking:
208 %elif repo_instance.enable_locking:
209 <p class="locking_unlocked">
209 <p class="locking_unlocked">
210 <i class="icon-repo-unlock"></i>
210 <i class="icon-repo-unlock"></i>
211 ${_('Repository not locked. Pull repository to lock it.')}
211 ${_('Repository not locked. Pull repository to lock it.')}
212 </p>
212 </p>
213 %endif
213 %endif
214
214
215 </div>
215 </div>
216 </%def>
216 </%def>
217
217
218 <%def name="repo_menu(active=None)">
218 <%def name="repo_menu(active=None)">
219 <%
219 <%
220 def is_active(selected):
220 def is_active(selected):
221 if selected == active:
221 if selected == active:
222 return "active"
222 return "active"
223 %>
223 %>
224
224
225 <!--- CONTEXT BAR -->
225 <!--- CONTEXT BAR -->
226 <div id="context-bar">
226 <div id="context-bar">
227 <div class="wrapper">
227 <div class="wrapper">
228 <ul id="context-pages" class="horizontal-list navigation">
228 <ul id="context-pages" class="horizontal-list navigation">
229 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
229 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
230 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
230 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
231 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
231 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
232 <li class="${is_active('compare')}">
232 <li class="${is_active('compare')}">
233 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
233 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
234 </li>
234 </li>
235 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
235 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
236 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
236 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
237 <li class="${is_active('showpullrequest')}">
237 <li class="${is_active('showpullrequest')}">
238 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
238 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
239 %if c.repository_pull_requests:
239 %if c.repository_pull_requests:
240 <span class="pr_notifications">${c.repository_pull_requests}</span>
240 <span class="pr_notifications">${c.repository_pull_requests}</span>
241 %endif
241 %endif
242 <div class="menulabel">${_('Pull Requests')}</div>
242 <div class="menulabel">${_('Pull Requests')}</div>
243 </a>
243 </a>
244 </li>
244 </li>
245 %endif
245 %endif
246 <li class="${is_active('options')}">
246 <li class="${is_active('options')}">
247 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
247 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
248 <ul class="submenu">
248 <ul class="submenu">
249 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
249 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
250 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
250 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
251 %endif
251 %endif
252 %if c.rhodecode_db_repo.fork:
252 %if c.rhodecode_db_repo.fork:
253 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
253 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
254 ${_('Compare fork')}</a></li>
254 ${_('Compare fork')}</a></li>
255 %endif
255 %endif
256
256
257 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
257 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
258
258
259 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
259 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
260 %if c.rhodecode_db_repo.locked[0]:
260 %if c.rhodecode_db_repo.locked[0]:
261 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
261 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
262 %else:
262 %else:
263 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
263 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
264 %endif
264 %endif
265 %endif
265 %endif
266 %if c.rhodecode_user.username != h.DEFAULT_USER:
266 %if c.rhodecode_user.username != h.DEFAULT_USER:
267 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
267 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
268 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
268 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
269 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
269 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
270 %endif
270 %endif
271 %endif
271 %endif
272 </ul>
272 </ul>
273 </li>
273 </li>
274 </ul>
274 </ul>
275 </div>
275 </div>
276 <div class="clear"></div>
276 <div class="clear"></div>
277 </div>
277 </div>
278 <!--- END CONTEXT BAR -->
278 <!--- END CONTEXT BAR -->
279
279
280 </%def>
280 </%def>
281
281
282 <%def name="usermenu()">
282 <%def name="usermenu()">
283 ## USER MENU
283 ## USER MENU
284 <li id="quick_login_li">
284 <li id="quick_login_li">
285 <a id="quick_login_link" class="menulink childs">
285 <a id="quick_login_link" class="menulink childs">
286 ${gravatar(c.rhodecode_user.email, 20)}
286 ${gravatar(c.rhodecode_user.email, 20)}
287 <span class="user">
287 <span class="user">
288 %if c.rhodecode_user.username != h.DEFAULT_USER:
288 %if c.rhodecode_user.username != h.DEFAULT_USER:
289 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
289 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
290 %else:
290 %else:
291 <span>${_('Sign in')}</span>
291 <span>${_('Sign in')}</span>
292 %endif
292 %endif
293 </span>
293 </span>
294 </a>
294 </a>
295
295
296 <div class="user-menu submenu">
296 <div class="user-menu submenu">
297 <div id="quick_login">
297 <div id="quick_login">
298 %if c.rhodecode_user.username == h.DEFAULT_USER:
298 %if c.rhodecode_user.username == h.DEFAULT_USER:
299 <h4>${_('Sign in to your account')}</h4>
299 <h4>${_('Sign in to your account')}</h4>
300 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
300 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
301 <div class="form form-vertical">
301 <div class="form form-vertical">
302 <div class="fields">
302 <div class="fields">
303 <div class="field">
303 <div class="field">
304 <div class="label">
304 <div class="label">
305 <label for="username">${_('Username')}:</label>
305 <label for="username">${_('Username')}:</label>
306 </div>
306 </div>
307 <div class="input">
307 <div class="input">
308 ${h.text('username',class_='focus',tabindex=1)}
308 ${h.text('username',class_='focus',tabindex=1)}
309 </div>
309 </div>
310
310
311 </div>
311 </div>
312 <div class="field">
312 <div class="field">
313 <div class="label">
313 <div class="label">
314 <label for="password">${_('Password')}:</label>
314 <label for="password">${_('Password')}:</label>
315 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'))}</span>
315 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'))}</span>
316 </div>
316 </div>
317 <div class="input">
317 <div class="input">
318 ${h.password('password',class_='focus',tabindex=2)}
318 ${h.password('password',class_='focus',tabindex=2)}
319 </div>
319 </div>
320 </div>
320 </div>
321 <div class="buttons">
321 <div class="buttons">
322 <div class="register">
322 <div class="register">
323 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
323 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
324 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
324 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
325 %endif
325 %endif
326 </div>
326 </div>
327 <div class="submit">
327 <div class="submit">
328 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
328 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
329 </div>
329 </div>
330 </div>
330 </div>
331 </div>
331 </div>
332 </div>
332 </div>
333 ${h.end_form()}
333 ${h.end_form()}
334 %else:
334 %else:
335 <div class="">
335 <div class="">
336 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
336 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
337 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
337 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
338 <div class="email">${c.rhodecode_user.email}</div>
338 <div class="email">${c.rhodecode_user.email}</div>
339 </div>
339 </div>
340 <div class="">
340 <div class="">
341 <ol class="links">
341 <ol class="links">
342 <li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
342 <li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
343 <li class="logout">
343 <li class="logout">
344 ${h.secure_form(h.route_path('logout'))}
344 ${h.secure_form(h.route_path('logout'))}
345 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
345 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
346 ${h.end_form()}
346 ${h.end_form()}
347 </li>
347 </li>
348 </ol>
348 </ol>
349 </div>
349 </div>
350 %endif
350 %endif
351 </div>
351 </div>
352 </div>
352 </div>
353 %if c.rhodecode_user.username != h.DEFAULT_USER:
353 %if c.rhodecode_user.username != h.DEFAULT_USER:
354 <div class="pill_container">
354 <div class="pill_container">
355 % if c.unread_notifications == 0:
355 % if c.unread_notifications == 0:
356 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
356 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
357 % else:
357 % else:
358 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
358 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
359 % endif
359 % endif
360 </div>
360 </div>
361 % endif
361 % endif
362 </li>
362 </li>
363 </%def>
363 </%def>
364
364
365 <%def name="menu_items(active=None)">
365 <%def name="menu_items(active=None)">
366 <%
366 <%
367 def is_active(selected):
367 def is_active(selected):
368 if selected == active:
368 if selected == active:
369 return "active"
369 return "active"
370 return ""
370 return ""
371 %>
371 %>
372 <ul id="quick" class="main_nav navigation horizontal-list">
372 <ul id="quick" class="main_nav navigation horizontal-list">
373 <!-- repo switcher -->
373 <!-- repo switcher -->
374 <li class="${is_active('repositories')} repo_switcher_li has_select2">
374 <li class="${is_active('repositories')} repo_switcher_li has_select2">
375 <input id="repo_switcher" name="repo_switcher" type="hidden">
375 <input id="repo_switcher" name="repo_switcher" type="hidden">
376 </li>
376 </li>
377
377
378 ## ROOT MENU
378 ## ROOT MENU
379 %if c.rhodecode_user.username != h.DEFAULT_USER:
379 %if c.rhodecode_user.username != h.DEFAULT_USER:
380 <li class="${is_active('journal')}">
380 <li class="${is_active('journal')}">
381 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
381 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
382 <div class="menulabel">${_('Journal')}</div>
382 <div class="menulabel">${_('Journal')}</div>
383 </a>
383 </a>
384 </li>
384 </li>
385 %else:
385 %else:
386 <li class="${is_active('journal')}">
386 <li class="${is_active('journal')}">
387 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
387 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
388 <div class="menulabel">${_('Public journal')}</div>
388 <div class="menulabel">${_('Public journal')}</div>
389 </a>
389 </a>
390 </li>
390 </li>
391 %endif
391 %endif
392 <li class="${is_active('gists')}">
392 <li class="${is_active('gists')}">
393 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
393 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
394 <div class="menulabel">${_('Gists')}</div>
394 <div class="menulabel">${_('Gists')}</div>
395 </a>
395 </a>
396 </li>
396 </li>
397 <li class="${is_active('search')}">
397 <li class="${is_active('search')}">
398 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
398 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
399 <div class="menulabel">${_('Search')}</div>
399 <div class="menulabel">${_('Search')}</div>
400 </a>
400 </a>
401 </li>
401 </li>
402 % if h.HasPermissionAll('hg.admin')('access admin main page'):
402 % if h.HasPermissionAll('hg.admin')('access admin main page'):
403 <li class="${is_active('admin')}">
403 <li class="${is_active('admin')}">
404 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
404 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
405 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
405 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
406 </a>
406 </a>
407 ${admin_menu()}
407 ${admin_menu()}
408 </li>
408 </li>
409 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
409 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
410 <li class="${is_active('admin')}">
410 <li class="${is_active('admin')}">
411 <a class="menulink childs" title="${_('Delegated Admin settings')}">
411 <a class="menulink childs" title="${_('Delegated Admin settings')}">
412 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
412 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
413 </a>
413 </a>
414 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
414 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
415 c.rhodecode_user.repository_groups_admin,
415 c.rhodecode_user.repository_groups_admin,
416 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
416 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
417 </li>
417 </li>
418 % endif
418 % endif
419 % if c.debug_style:
419 % if c.debug_style:
420 <li class="${is_active('debug_style')}">
420 <li class="${is_active('debug_style')}">
421 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
421 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
422 <div class="menulabel">${_('Style')}</div>
422 <div class="menulabel">${_('Style')}</div>
423 </a>
423 </a>
424 </li>
424 </li>
425 % endif
425 % endif
426 ## render extra user menu
426 ## render extra user menu
427 ${usermenu()}
427 ${usermenu()}
428 </ul>
428 </ul>
429
429
430 <script type="text/javascript">
430 <script type="text/javascript">
431 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
431 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
432
432
433 /*format the look of items in the list*/
433 /*format the look of items in the list*/
434 var format = function(state, escapeMarkup){
434 var format = function(state, escapeMarkup){
435 if (!state.id){
435 if (!state.id){
436 return state.text; // optgroup
436 return state.text; // optgroup
437 }
437 }
438 var obj_dict = state.obj;
438 var obj_dict = state.obj;
439 var tmpl = '';
439 var tmpl = '';
440
440
441 if(obj_dict && state.type == 'repo'){
441 if(obj_dict && state.type == 'repo'){
442 if(obj_dict['repo_type'] === 'hg'){
442 if(obj_dict['repo_type'] === 'hg'){
443 tmpl += '<i class="icon-hg"></i> ';
443 tmpl += '<i class="icon-hg"></i> ';
444 }
444 }
445 else if(obj_dict['repo_type'] === 'git'){
445 else if(obj_dict['repo_type'] === 'git'){
446 tmpl += '<i class="icon-git"></i> ';
446 tmpl += '<i class="icon-git"></i> ';
447 }
447 }
448 else if(obj_dict['repo_type'] === 'svn'){
448 else if(obj_dict['repo_type'] === 'svn'){
449 tmpl += '<i class="icon-svn"></i> ';
449 tmpl += '<i class="icon-svn"></i> ';
450 }
450 }
451 if(obj_dict['private']){
451 if(obj_dict['private']){
452 tmpl += '<i class="icon-lock" ></i> ';
452 tmpl += '<i class="icon-lock" ></i> ';
453 }
453 }
454 else if(visual_show_public_icon){
454 else if(visual_show_public_icon){
455 tmpl += '<i class="icon-unlock-alt"></i> ';
455 tmpl += '<i class="icon-unlock-alt"></i> ';
456 }
456 }
457 }
457 }
458 if(obj_dict && state.type == 'commit') {
458 if(obj_dict && state.type == 'commit') {
459 tmpl += '<i class="icon-tag"></i>';
459 tmpl += '<i class="icon-tag"></i>';
460 }
460 }
461 if(obj_dict && state.type == 'group'){
461 if(obj_dict && state.type == 'group'){
462 tmpl += '<i class="icon-folder-close"></i> ';
462 tmpl += '<i class="icon-folder-close"></i> ';
463 }
463 }
464 tmpl += escapeMarkup(state.text);
464 tmpl += escapeMarkup(state.text);
465 return tmpl;
465 return tmpl;
466 };
466 };
467
467
468 var formatResult = function(result, container, query, escapeMarkup) {
468 var formatResult = function(result, container, query, escapeMarkup) {
469 return format(result, escapeMarkup);
469 return format(result, escapeMarkup);
470 };
470 };
471
471
472 var formatSelection = function(data, container, escapeMarkup) {
472 var formatSelection = function(data, container, escapeMarkup) {
473 return format(data, escapeMarkup);
473 return format(data, escapeMarkup);
474 };
474 };
475
475
476 $("#repo_switcher").select2({
476 $("#repo_switcher").select2({
477 cachedDataSource: {},
477 cachedDataSource: {},
478 minimumInputLength: 2,
478 minimumInputLength: 2,
479 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
479 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
480 dropdownAutoWidth: true,
480 dropdownAutoWidth: true,
481 formatResult: formatResult,
481 formatResult: formatResult,
482 formatSelection: formatSelection,
482 formatSelection: formatSelection,
483 containerCssClass: "repo-switcher",
483 containerCssClass: "repo-switcher",
484 dropdownCssClass: "repo-switcher-dropdown",
484 dropdownCssClass: "repo-switcher-dropdown",
485 escapeMarkup: function(m){
485 escapeMarkup: function(m){
486 // don't escape our custom placeholder
486 // don't escape our custom placeholder
487 if(m.substr(0,23) == '<div class="menulabel">'){
487 if(m.substr(0,23) == '<div class="menulabel">'){
488 return m;
488 return m;
489 }
489 }
490
490
491 return Select2.util.escapeMarkup(m);
491 return Select2.util.escapeMarkup(m);
492 },
492 },
493 query: $.debounce(250, function(query){
493 query: $.debounce(250, function(query){
494 self = this;
494 self = this;
495 var cacheKey = query.term;
495 var cacheKey = query.term;
496 var cachedData = self.cachedDataSource[cacheKey];
496 var cachedData = self.cachedDataSource[cacheKey];
497
497
498 if (cachedData) {
498 if (cachedData) {
499 query.callback({results: cachedData.results});
499 query.callback({results: cachedData.results});
500 } else {
500 } else {
501 $.ajax({
501 $.ajax({
502 url: "${h.url('goto_switcher_data')}",
502 url: "${h.url('goto_switcher_data')}",
503 data: {'query': query.term},
503 data: {'query': query.term},
504 dataType: 'json',
504 dataType: 'json',
505 type: 'GET',
505 type: 'GET',
506 success: function(data) {
506 success: function(data) {
507 self.cachedDataSource[cacheKey] = data;
507 self.cachedDataSource[cacheKey] = data;
508 query.callback({results: data.results});
508 query.callback({results: data.results});
509 },
509 },
510 error: function(data, textStatus, errorThrown) {
510 error: function(data, textStatus, errorThrown) {
511 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
511 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
512 }
512 }
513 })
513 })
514 }
514 }
515 })
515 })
516 });
516 });
517
517
518 $("#repo_switcher").on('select2-selecting', function(e){
518 $("#repo_switcher").on('select2-selecting', function(e){
519 e.preventDefault();
519 e.preventDefault();
520 window.location = e.choice.url;
520 window.location = e.choice.url;
521 });
521 });
522
522
523 ## Global mouse bindings ##
523 ## Global mouse bindings ##
524
524
525 // general help "?"
525 // general help "?"
526 Mousetrap.bind(['?'], function(e) {
526 Mousetrap.bind(['?'], function(e) {
527 $('#help_kb').modal({})
527 $('#help_kb').modal({})
528 });
528 });
529
529
530 // / open the quick filter
530 // / open the quick filter
531 Mousetrap.bind(['/'], function(e) {
531 Mousetrap.bind(['/'], function(e) {
532 $("#repo_switcher").select2("open");
532 $("#repo_switcher").select2("open");
533
533
534 // return false to prevent default browser behavior
534 // return false to prevent default browser behavior
535 // and stop event from bubbling
535 // and stop event from bubbling
536 return false;
536 return false;
537 });
537 });
538
538
539 // general nav g + action
539 // general nav g + action
540 Mousetrap.bind(['g h'], function(e) {
540 Mousetrap.bind(['g h'], function(e) {
541 window.location = pyroutes.url('home');
541 window.location = pyroutes.url('home');
542 });
542 });
543 Mousetrap.bind(['g g'], function(e) {
543 Mousetrap.bind(['g g'], function(e) {
544 window.location = pyroutes.url('gists', {'private':1});
544 window.location = pyroutes.url('gists', {'private':1});
545 });
545 });
546 Mousetrap.bind(['g G'], function(e) {
546 Mousetrap.bind(['g G'], function(e) {
547 window.location = pyroutes.url('gists', {'public':1});
547 window.location = pyroutes.url('gists', {'public':1});
548 });
548 });
549 Mousetrap.bind(['n g'], function(e) {
549 Mousetrap.bind(['n g'], function(e) {
550 window.location = pyroutes.url('new_gist');
550 window.location = pyroutes.url('new_gist');
551 });
551 });
552 Mousetrap.bind(['n r'], function(e) {
552 Mousetrap.bind(['n r'], function(e) {
553 window.location = pyroutes.url('new_repo');
553 window.location = pyroutes.url('new_repo');
554 });
554 });
555
555
556 % if hasattr(c, 'repo_name') and hasattr(c, 'rhodecode_db_repo'):
556 % if hasattr(c, 'repo_name') and hasattr(c, 'rhodecode_db_repo'):
557 // nav in repo context
557 // nav in repo context
558 Mousetrap.bind(['g s'], function(e) {
558 Mousetrap.bind(['g s'], function(e) {
559 window.location = pyroutes.url('summary_home', {'repo_name': REPO_NAME});
559 window.location = pyroutes.url('summary_home', {'repo_name': REPO_NAME});
560 });
560 });
561 Mousetrap.bind(['g c'], function(e) {
561 Mousetrap.bind(['g c'], function(e) {
562 window.location = pyroutes.url('changelog_home', {'repo_name': REPO_NAME});
562 window.location = pyroutes.url('changelog_home', {'repo_name': REPO_NAME});
563 });
563 });
564 Mousetrap.bind(['g F'], function(e) {
564 Mousetrap.bind(['g F'], function(e) {
565 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': '', 'search': '1'});
565 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': '', 'search': '1'});
566 });
566 });
567 Mousetrap.bind(['g f'], function(e) {
567 Mousetrap.bind(['g f'], function(e) {
568 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': ''});
568 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': ''});
569 });
569 });
570 Mousetrap.bind(['g p'], function(e) {
570 Mousetrap.bind(['g p'], function(e) {
571 window.location = pyroutes.url('pullrequest_show_all', {'repo_name': REPO_NAME});
571 window.location = pyroutes.url('pullrequest_show_all', {'repo_name': REPO_NAME});
572 });
572 });
573 Mousetrap.bind(['g o'], function(e) {
573 Mousetrap.bind(['g o'], function(e) {
574 window.location = pyroutes.url('edit_repo', {'repo_name': REPO_NAME});
574 window.location = pyroutes.url('edit_repo', {'repo_name': REPO_NAME});
575 });
575 });
576 Mousetrap.bind(['g O'], function(e) {
576 Mousetrap.bind(['g O'], function(e) {
577 window.location = pyroutes.url('edit_repo_perms', {'repo_name': REPO_NAME});
577 window.location = pyroutes.url('edit_repo_perms', {'repo_name': REPO_NAME});
578 });
578 });
579 % endif
579 % endif
580
580
581 </script>
581 </script>
582 <script src="${h.url('/js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
582 <script src="${h.url('/js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
583 </%def>
583 </%def>
584
584
585 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
585 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
586 <div class="modal-dialog">
586 <div class="modal-dialog">
587 <div class="modal-content">
587 <div class="modal-content">
588 <div class="modal-header">
588 <div class="modal-header">
589 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
589 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
590 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
590 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
591 </div>
591 </div>
592 <div class="modal-body">
592 <div class="modal-body">
593 <div class="block-left">
593 <div class="block-left">
594 <table class="keyboard-mappings">
594 <table class="keyboard-mappings">
595 <tbody>
595 <tbody>
596 <tr>
596 <tr>
597 <th></th>
597 <th></th>
598 <th>${_('Site-wide shortcuts')}</th>
598 <th>${_('Site-wide shortcuts')}</th>
599 </tr>
599 </tr>
600 <%
600 <%
601 elems = [
601 elems = [
602 ('/', 'Open quick search box'),
602 ('/', 'Open quick search box'),
603 ('g h', 'Goto home page'),
603 ('g h', 'Goto home page'),
604 ('g g', 'Goto my private gists page'),
604 ('g g', 'Goto my private gists page'),
605 ('g G', 'Goto my public gists page'),
605 ('g G', 'Goto my public gists page'),
606 ('n r', 'New repository page'),
606 ('n r', 'New repository page'),
607 ('n g', 'New gist page'),
607 ('n g', 'New gist page'),
608 ]
608 ]
609 %>
609 %>
610 %for key, desc in elems:
610 %for key, desc in elems:
611 <tr>
611 <tr>
612 <td class="keys">
612 <td class="keys">
613 <span class="key tag">${key}</span>
613 <span class="key tag">${key}</span>
614 </td>
614 </td>
615 <td>${desc}</td>
615 <td>${desc}</td>
616 </tr>
616 </tr>
617 %endfor
617 %endfor
618 </tbody>
618 </tbody>
619 </table>
619 </table>
620 </div>
620 </div>
621 <div class="block-left">
621 <div class="block-left">
622 <table class="keyboard-mappings">
622 <table class="keyboard-mappings">
623 <tbody>
623 <tbody>
624 <tr>
624 <tr>
625 <th></th>
625 <th></th>
626 <th>${_('Repositories')}</th>
626 <th>${_('Repositories')}</th>
627 </tr>
627 </tr>
628 <%
628 <%
629 elems = [
629 elems = [
630 ('g s', 'Goto summary page'),
630 ('g s', 'Goto summary page'),
631 ('g c', 'Goto changelog page'),
631 ('g c', 'Goto changelog page'),
632 ('g f', 'Goto files page'),
632 ('g f', 'Goto files page'),
633 ('g F', 'Goto files page with file search activated'),
633 ('g F', 'Goto files page with file search activated'),
634 ('g p', 'Goto pull requests page'),
634 ('g p', 'Goto pull requests page'),
635 ('g o', 'Goto repository settings'),
635 ('g o', 'Goto repository settings'),
636 ('g O', 'Goto repository permissions settings'),
636 ('g O', 'Goto repository permissions settings'),
637 ]
637 ]
638 %>
638 %>
639 %for key, desc in elems:
639 %for key, desc in elems:
640 <tr>
640 <tr>
641 <td class="keys">
641 <td class="keys">
642 <span class="key tag">${key}</span>
642 <span class="key tag">${key}</span>
643 </td>
643 </td>
644 <td>${desc}</td>
644 <td>${desc}</td>
645 </tr>
645 </tr>
646 %endfor
646 %endfor
647 </tbody>
647 </tbody>
648 </table>
648 </table>
649 </div>
649 </div>
650 </div>
650 </div>
651 <div class="modal-footer">
651 <div class="modal-footer">
652 </div>
652 </div>
653 </div><!-- /.modal-content -->
653 </div><!-- /.modal-content -->
654 </div><!-- /.modal-dialog -->
654 </div><!-- /.modal-dialog -->
655 </div><!-- /.modal -->
655 </div><!-- /.modal -->
General Comments 0
You need to be logged in to leave comments. Login now