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