##// END OF EJS Templates
fixed problem with tooltips flickering.
marcink -
r1187:08d77411 beta
parent child Browse files
Show More
@@ -1,700 +1,642
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10
10
11 from datetime import datetime
11 from datetime import datetime
12 from pygments.formatters import HtmlFormatter
12 from pygments.formatters import HtmlFormatter
13 from pygments import highlight as code_highlight
13 from pygments import highlight as code_highlight
14 from pylons import url, request, config
14 from pylons import url, request, config
15 from pylons.i18n.translation import _, ungettext
15 from pylons.i18n.translation import _, ungettext
16
16
17 from webhelpers.html import literal, HTML, escape
17 from webhelpers.html import literal, HTML, escape
18 from webhelpers.html.tools import *
18 from webhelpers.html.tools import *
19 from webhelpers.html.builder import make_tag
19 from webhelpers.html.builder import make_tag
20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
23 password, textarea, title, ul, xml_declaration, radio
23 password, textarea, title, ul, xml_declaration, radio
24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
25 mail_to, strip_links, strip_tags, tag_re
25 mail_to, strip_links, strip_tags, tag_re
26 from webhelpers.number import format_byte_size, format_bit_size
26 from webhelpers.number import format_byte_size, format_bit_size
27 from webhelpers.pylonslib import Flash as _Flash
27 from webhelpers.pylonslib import Flash as _Flash
28 from webhelpers.pylonslib.secure_form import secure_form
28 from webhelpers.pylonslib.secure_form import secure_form
29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
31 replace_whitespace, urlify, truncate, wrap_paragraphs
31 replace_whitespace, urlify, truncate, wrap_paragraphs
32 from webhelpers.date import time_ago_in_words
32 from webhelpers.date import time_ago_in_words
33 from webhelpers.paginate import Page
33 from webhelpers.paginate import Page
34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
35 convert_boolean_attrs, NotGiven
35 convert_boolean_attrs, NotGiven
36
36
37 from vcs.utils.annotate import annotate_highlight
37 from vcs.utils.annotate import annotate_highlight
38 from rhodecode.lib.utils import repo_name_slug
38 from rhodecode.lib.utils import repo_name_slug
39 from rhodecode.lib import str2bool, safe_unicode
39 from rhodecode.lib import str2bool, safe_unicode
40
40
41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
42 """
42 """
43 Reset button
43 Reset button
44 """
44 """
45 _set_input_attrs(attrs, type, name, value)
45 _set_input_attrs(attrs, type, name, value)
46 _set_id_attr(attrs, id, name)
46 _set_id_attr(attrs, id, name)
47 convert_boolean_attrs(attrs, ["disabled"])
47 convert_boolean_attrs(attrs, ["disabled"])
48 return HTML.input(**attrs)
48 return HTML.input(**attrs)
49
49
50 reset = _reset
50 reset = _reset
51
51
52
52
53 def get_token():
53 def get_token():
54 """Return the current authentication token, creating one if one doesn't
54 """Return the current authentication token, creating one if one doesn't
55 already exist.
55 already exist.
56 """
56 """
57 token_key = "_authentication_token"
57 token_key = "_authentication_token"
58 from pylons import session
58 from pylons import session
59 if not token_key in session:
59 if not token_key in session:
60 try:
60 try:
61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
62 except AttributeError: # Python < 2.4
62 except AttributeError: # Python < 2.4
63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
64 session[token_key] = token
64 session[token_key] = token
65 if hasattr(session, 'save'):
65 if hasattr(session, 'save'):
66 session.save()
66 session.save()
67 return session[token_key]
67 return session[token_key]
68
68
69 class _GetError(object):
69 class _GetError(object):
70 """Get error from form_errors, and represent it as span wrapped error
70 """Get error from form_errors, and represent it as span wrapped error
71 message
71 message
72
72
73 :param field_name: field to fetch errors for
73 :param field_name: field to fetch errors for
74 :param form_errors: form errors dict
74 :param form_errors: form errors dict
75 """
75 """
76
76
77 def __call__(self, field_name, form_errors):
77 def __call__(self, field_name, form_errors):
78 tmpl = """<span class="error_msg">%s</span>"""
78 tmpl = """<span class="error_msg">%s</span>"""
79 if form_errors and form_errors.has_key(field_name):
79 if form_errors and form_errors.has_key(field_name):
80 return literal(tmpl % form_errors.get(field_name))
80 return literal(tmpl % form_errors.get(field_name))
81
81
82 get_error = _GetError()
82 get_error = _GetError()
83
83
84 class _ToolTip(object):
84 class _ToolTip(object):
85
85
86 def __call__(self, tooltip_title, trim_at=50):
86 def __call__(self, tooltip_title, trim_at=50):
87 """Special function just to wrap our text into nice formatted
87 """Special function just to wrap our text into nice formatted
88 autowrapped text
88 autowrapped text
89
89
90 :param tooltip_title:
90 :param tooltip_title:
91 """
91 """
92
92
93 return wrap_paragraphs(escape(tooltip_title), trim_at)\
93 return wrap_paragraphs(escape(tooltip_title), trim_at)\
94 .replace('\n', '<br/>')
94 .replace('\n', '<br/>')
95
95
96 def activate(self):
96 def activate(self):
97 """Adds tooltip mechanism to the given Html all tooltips have to have
97 """Adds tooltip mechanism to the given Html all tooltips have to have
98 set class `tooltip` and set attribute `tooltip_title`.
98 set class `tooltip` and set attribute `tooltip_title`.
99 Then a tooltip will be generated based on that. All with yui js tooltip
99 Then a tooltip will be generated based on that. All with yui js tooltip
100 """
100 """
101
101
102 js = '''
102 js = '''
103 YAHOO.util.Event.onDOMReady(function(){
103 YAHOO.util.Event.onDOMReady(function(){
104 function toolTipsId(){
104 function toolTipsId(){
105 var ids = [];
105 var ids = [];
106 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
106 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
107
107
108 for (var i = 0; i < tts.length; i++) {
108 for (var i = 0; i < tts.length; i++) {
109 //if element doesn't not have and id autogenerate one for tooltip
109 //if element doesn't not have and id autogenerate one for tooltip
110
110
111 if (!tts[i].id){
111 if (!tts[i].id){
112 tts[i].id='tt'+i*100;
112 tts[i].id='tt'+i*100;
113 }
113 }
114 ids.push(tts[i].id);
114 ids.push(tts[i].id);
115 }
115 }
116 return ids
116 return ids
117 };
117 };
118 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
118 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
119 context: toolTipsId(),
119 context: [[toolTipsId()],"tl","bl",null,[0,5]],
120 monitorresize:false,
120 monitorresize:false,
121 xyoffset :[0,0],
121 xyoffset :[0,0],
122 autodismissdelay:300000,
122 autodismissdelay:300000,
123 hidedelay:5,
123 hidedelay:5,
124 showdelay:20,
124 showdelay:20,
125 });
125 });
126
126
127 // Set the text for the tooltip just before we display it. Lazy method
128 myToolTips.contextTriggerEvent.subscribe(
129 function(type, args) {
130
131 var context = args[0];
132
133 //positioning of tooltip
134 var tt_w = this.element.clientWidth;//tooltip width
135 var tt_h = this.element.clientHeight;//tooltip height
136
137 var context_w = context.offsetWidth;
138 var context_h = context.offsetHeight;
139
140 var pos_x = YAHOO.util.Dom.getX(context);
141 var pos_y = YAHOO.util.Dom.getY(context);
142
143 var display_strategy = 'right';
144 var xy_pos = [0,0];
145 switch (display_strategy){
146
147 case 'top':
148 var cur_x = (pos_x+context_w/2)-(tt_w/2);
149 var cur_y = (pos_y-tt_h-4);
150 xy_pos = [cur_x,cur_y];
151 break;
152 case 'bottom':
153 var cur_x = (pos_x+context_w/2)-(tt_w/2);
154 var cur_y = pos_y+context_h+4;
155 xy_pos = [cur_x,cur_y];
156 break;
157 case 'left':
158 var cur_x = (pos_x-tt_w-4);
159 var cur_y = pos_y-((tt_h/2)-context_h/2);
160 xy_pos = [cur_x,cur_y];
161 break;
162 case 'right':
163 var cur_x = (pos_x+context_w+4);
164 var cur_y = pos_y-((tt_h/2)-context_h/2);
165 xy_pos = [cur_x,cur_y];
166 break;
167 default:
168 var cur_x = (pos_x+context_w/2)-(tt_w/2);
169 var cur_y = pos_y-tt_h-4;
170 xy_pos = [cur_x,cur_y];
171 break;
172
173 }
174
175 this.cfg.setProperty("xy",xy_pos);
176
177 });
178
179 //Mouse out
180 myToolTips.contextMouseOutEvent.subscribe(
181 function(type, args) {
182 var context = args[0];
183
184 });
185 });
127 });
186 '''
128 '''
187 return literal(js)
129 return literal(js)
188
130
189 tooltip = _ToolTip()
131 tooltip = _ToolTip()
190
132
191 class _FilesBreadCrumbs(object):
133 class _FilesBreadCrumbs(object):
192
134
193 def __call__(self, repo_name, rev, paths):
135 def __call__(self, repo_name, rev, paths):
194 if isinstance(paths, str):
136 if isinstance(paths, str):
195 paths = safe_unicode(paths)
137 paths = safe_unicode(paths)
196 url_l = [link_to(repo_name, url('files_home',
138 url_l = [link_to(repo_name, url('files_home',
197 repo_name=repo_name,
139 repo_name=repo_name,
198 revision=rev, f_path=''))]
140 revision=rev, f_path=''))]
199 paths_l = paths.split('/')
141 paths_l = paths.split('/')
200 for cnt, p in enumerate(paths_l):
142 for cnt, p in enumerate(paths_l):
201 if p != '':
143 if p != '':
202 url_l.append(link_to(p, url('files_home',
144 url_l.append(link_to(p, url('files_home',
203 repo_name=repo_name,
145 repo_name=repo_name,
204 revision=rev,
146 revision=rev,
205 f_path='/'.join(paths_l[:cnt + 1]))))
147 f_path='/'.join(paths_l[:cnt + 1]))))
206
148
207 return literal('/'.join(url_l))
149 return literal('/'.join(url_l))
208
150
209 files_breadcrumbs = _FilesBreadCrumbs()
151 files_breadcrumbs = _FilesBreadCrumbs()
210
152
211 class CodeHtmlFormatter(HtmlFormatter):
153 class CodeHtmlFormatter(HtmlFormatter):
212 """My code Html Formatter for source codes
154 """My code Html Formatter for source codes
213 """
155 """
214
156
215 def wrap(self, source, outfile):
157 def wrap(self, source, outfile):
216 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
158 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
217
159
218 def _wrap_code(self, source):
160 def _wrap_code(self, source):
219 for cnt, it in enumerate(source):
161 for cnt, it in enumerate(source):
220 i, t = it
162 i, t = it
221 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
163 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
222 yield i, t
164 yield i, t
223
165
224 def _wrap_tablelinenos(self, inner):
166 def _wrap_tablelinenos(self, inner):
225 dummyoutfile = StringIO.StringIO()
167 dummyoutfile = StringIO.StringIO()
226 lncount = 0
168 lncount = 0
227 for t, line in inner:
169 for t, line in inner:
228 if t:
170 if t:
229 lncount += 1
171 lncount += 1
230 dummyoutfile.write(line)
172 dummyoutfile.write(line)
231
173
232 fl = self.linenostart
174 fl = self.linenostart
233 mw = len(str(lncount + fl - 1))
175 mw = len(str(lncount + fl - 1))
234 sp = self.linenospecial
176 sp = self.linenospecial
235 st = self.linenostep
177 st = self.linenostep
236 la = self.lineanchors
178 la = self.lineanchors
237 aln = self.anchorlinenos
179 aln = self.anchorlinenos
238 nocls = self.noclasses
180 nocls = self.noclasses
239 if sp:
181 if sp:
240 lines = []
182 lines = []
241
183
242 for i in range(fl, fl + lncount):
184 for i in range(fl, fl + lncount):
243 if i % st == 0:
185 if i % st == 0:
244 if i % sp == 0:
186 if i % sp == 0:
245 if aln:
187 if aln:
246 lines.append('<a href="#%s%d" class="special">%*d</a>' %
188 lines.append('<a href="#%s%d" class="special">%*d</a>' %
247 (la, i, mw, i))
189 (la, i, mw, i))
248 else:
190 else:
249 lines.append('<span class="special">%*d</span>' % (mw, i))
191 lines.append('<span class="special">%*d</span>' % (mw, i))
250 else:
192 else:
251 if aln:
193 if aln:
252 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
194 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
253 else:
195 else:
254 lines.append('%*d' % (mw, i))
196 lines.append('%*d' % (mw, i))
255 else:
197 else:
256 lines.append('')
198 lines.append('')
257 ls = '\n'.join(lines)
199 ls = '\n'.join(lines)
258 else:
200 else:
259 lines = []
201 lines = []
260 for i in range(fl, fl + lncount):
202 for i in range(fl, fl + lncount):
261 if i % st == 0:
203 if i % st == 0:
262 if aln:
204 if aln:
263 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
205 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
264 else:
206 else:
265 lines.append('%*d' % (mw, i))
207 lines.append('%*d' % (mw, i))
266 else:
208 else:
267 lines.append('')
209 lines.append('')
268 ls = '\n'.join(lines)
210 ls = '\n'.join(lines)
269
211
270 # in case you wonder about the seemingly redundant <div> here: since the
212 # in case you wonder about the seemingly redundant <div> here: since the
271 # content in the other cell also is wrapped in a div, some browsers in
213 # content in the other cell also is wrapped in a div, some browsers in
272 # some configurations seem to mess up the formatting...
214 # some configurations seem to mess up the formatting...
273 if nocls:
215 if nocls:
274 yield 0, ('<table class="%stable">' % self.cssclass +
216 yield 0, ('<table class="%stable">' % self.cssclass +
275 '<tr><td><div class="linenodiv" '
217 '<tr><td><div class="linenodiv" '
276 'style="background-color: #f0f0f0; padding-right: 10px">'
218 'style="background-color: #f0f0f0; padding-right: 10px">'
277 '<pre style="line-height: 125%">' +
219 '<pre style="line-height: 125%">' +
278 ls + '</pre></div></td><td class="code">')
220 ls + '</pre></div></td><td class="code">')
279 else:
221 else:
280 yield 0, ('<table class="%stable">' % self.cssclass +
222 yield 0, ('<table class="%stable">' % self.cssclass +
281 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
223 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
282 ls + '</pre></div></td><td class="code">')
224 ls + '</pre></div></td><td class="code">')
283 yield 0, dummyoutfile.getvalue()
225 yield 0, dummyoutfile.getvalue()
284 yield 0, '</td></tr></table>'
226 yield 0, '</td></tr></table>'
285
227
286
228
287 def pygmentize(filenode, **kwargs):
229 def pygmentize(filenode, **kwargs):
288 """pygmentize function using pygments
230 """pygmentize function using pygments
289
231
290 :param filenode:
232 :param filenode:
291 """
233 """
292
234
293 return literal(code_highlight(filenode.content,
235 return literal(code_highlight(filenode.content,
294 filenode.lexer, CodeHtmlFormatter(**kwargs)))
236 filenode.lexer, CodeHtmlFormatter(**kwargs)))
295
237
296 def pygmentize_annotation(repo_name, filenode, **kwargs):
238 def pygmentize_annotation(repo_name, filenode, **kwargs):
297 """pygmentize function for annotation
239 """pygmentize function for annotation
298
240
299 :param filenode:
241 :param filenode:
300 """
242 """
301
243
302 color_dict = {}
244 color_dict = {}
303 def gen_color(n=10000):
245 def gen_color(n=10000):
304 """generator for getting n of evenly distributed colors using
246 """generator for getting n of evenly distributed colors using
305 hsv color and golden ratio. It always return same order of colors
247 hsv color and golden ratio. It always return same order of colors
306
248
307 :returns: RGB tuple
249 :returns: RGB tuple
308 """
250 """
309 import colorsys
251 import colorsys
310 golden_ratio = 0.618033988749895
252 golden_ratio = 0.618033988749895
311 h = 0.22717784590367374
253 h = 0.22717784590367374
312
254
313 for c in xrange(n):
255 for c in xrange(n):
314 h += golden_ratio
256 h += golden_ratio
315 h %= 1
257 h %= 1
316 HSV_tuple = [h, 0.95, 0.95]
258 HSV_tuple = [h, 0.95, 0.95]
317 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
259 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
318 yield map(lambda x:str(int(x * 256)), RGB_tuple)
260 yield map(lambda x:str(int(x * 256)), RGB_tuple)
319
261
320 cgenerator = gen_color()
262 cgenerator = gen_color()
321
263
322 def get_color_string(cs):
264 def get_color_string(cs):
323 if color_dict.has_key(cs):
265 if color_dict.has_key(cs):
324 col = color_dict[cs]
266 col = color_dict[cs]
325 else:
267 else:
326 col = color_dict[cs] = cgenerator.next()
268 col = color_dict[cs] = cgenerator.next()
327 return "color: rgb(%s)! important;" % (', '.join(col))
269 return "color: rgb(%s)! important;" % (', '.join(col))
328
270
329 def url_func(repo_name):
271 def url_func(repo_name):
330 def _url_func(changeset):
272 def _url_func(changeset):
331 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
273 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
332 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
274 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
333
275
334 tooltip_html = tooltip_html % (changeset.author,
276 tooltip_html = tooltip_html % (changeset.author,
335 changeset.date,
277 changeset.date,
336 tooltip(changeset.message))
278 tooltip(changeset.message))
337 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
279 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
338 short_id(changeset.raw_id))
280 short_id(changeset.raw_id))
339 uri = link_to(
281 uri = link_to(
340 lnk_format,
282 lnk_format,
341 url('changeset_home', repo_name=repo_name,
283 url('changeset_home', repo_name=repo_name,
342 revision=changeset.raw_id),
284 revision=changeset.raw_id),
343 style=get_color_string(changeset.raw_id),
285 style=get_color_string(changeset.raw_id),
344 class_='tooltip',
286 class_='tooltip',
345 title=tooltip_html
287 title=tooltip_html
346 )
288 )
347
289
348 uri += '\n'
290 uri += '\n'
349 return uri
291 return uri
350 return _url_func
292 return _url_func
351
293
352 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
294 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
353
295
354 def get_changeset_safe(repo, rev):
296 def get_changeset_safe(repo, rev):
355 from vcs.backends.base import BaseRepository
297 from vcs.backends.base import BaseRepository
356 from vcs.exceptions import RepositoryError
298 from vcs.exceptions import RepositoryError
357 if not isinstance(repo, BaseRepository):
299 if not isinstance(repo, BaseRepository):
358 raise Exception('You must pass an Repository '
300 raise Exception('You must pass an Repository '
359 'object as first argument got %s', type(repo))
301 'object as first argument got %s', type(repo))
360
302
361 try:
303 try:
362 cs = repo.get_changeset(rev)
304 cs = repo.get_changeset(rev)
363 except RepositoryError:
305 except RepositoryError:
364 from rhodecode.lib.utils import EmptyChangeset
306 from rhodecode.lib.utils import EmptyChangeset
365 cs = EmptyChangeset()
307 cs = EmptyChangeset()
366 return cs
308 return cs
367
309
368
310
369 def is_following_repo(repo_name, user_id):
311 def is_following_repo(repo_name, user_id):
370 from rhodecode.model.scm import ScmModel
312 from rhodecode.model.scm import ScmModel
371 return ScmModel().is_following_repo(repo_name, user_id)
313 return ScmModel().is_following_repo(repo_name, user_id)
372
314
373 flash = _Flash()
315 flash = _Flash()
374
316
375
317
376 #==============================================================================
318 #==============================================================================
377 # MERCURIAL FILTERS available via h.
319 # MERCURIAL FILTERS available via h.
378 #==============================================================================
320 #==============================================================================
379 from mercurial import util
321 from mercurial import util
380 from mercurial.templatefilters import person as _person
322 from mercurial.templatefilters import person as _person
381
323
382 def _age(curdate):
324 def _age(curdate):
383 """turns a datetime into an age string."""
325 """turns a datetime into an age string."""
384
326
385 if not curdate:
327 if not curdate:
386 return ''
328 return ''
387
329
388 agescales = [("year", 3600 * 24 * 365),
330 agescales = [("year", 3600 * 24 * 365),
389 ("month", 3600 * 24 * 30),
331 ("month", 3600 * 24 * 30),
390 ("day", 3600 * 24),
332 ("day", 3600 * 24),
391 ("hour", 3600),
333 ("hour", 3600),
392 ("minute", 60),
334 ("minute", 60),
393 ("second", 1), ]
335 ("second", 1), ]
394
336
395 age = datetime.now() - curdate
337 age = datetime.now() - curdate
396 age_seconds = (age.days * agescales[2][1]) + age.seconds
338 age_seconds = (age.days * agescales[2][1]) + age.seconds
397 pos = 1
339 pos = 1
398 for scale in agescales:
340 for scale in agescales:
399 if scale[1] <= age_seconds:
341 if scale[1] <= age_seconds:
400 if pos == 6:pos = 5
342 if pos == 6:pos = 5
401 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
343 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
402 pos += 1
344 pos += 1
403
345
404 return _('just now')
346 return _('just now')
405
347
406 age = lambda x:_age(x)
348 age = lambda x:_age(x)
407 capitalize = lambda x: x.capitalize()
349 capitalize = lambda x: x.capitalize()
408 email = util.email
350 email = util.email
409 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
351 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
410 person = lambda x: _person(x)
352 person = lambda x: _person(x)
411 short_id = lambda x: x[:12]
353 short_id = lambda x: x[:12]
412
354
413
355
414 def bool2icon(value):
356 def bool2icon(value):
415 """Returns True/False values represented as small html image of true/false
357 """Returns True/False values represented as small html image of true/false
416 icons
358 icons
417
359
418 :param value: bool value
360 :param value: bool value
419 """
361 """
420
362
421 if value is True:
363 if value is True:
422 return HTML.tag('img', src=url("/images/icons/accept.png"),
364 return HTML.tag('img', src=url("/images/icons/accept.png"),
423 alt=_('True'))
365 alt=_('True'))
424
366
425 if value is False:
367 if value is False:
426 return HTML.tag('img', src=url("/images/icons/cancel.png"),
368 return HTML.tag('img', src=url("/images/icons/cancel.png"),
427 alt=_('False'))
369 alt=_('False'))
428
370
429 return value
371 return value
430
372
431
373
432 def action_parser(user_log, feed=False):
374 def action_parser(user_log, feed=False):
433 """This helper will action_map the specified string action into translated
375 """This helper will action_map the specified string action into translated
434 fancy names with icons and links
376 fancy names with icons and links
435
377
436 :param user_log: user log instance
378 :param user_log: user log instance
437 :param feed: use output for feeds (no html and fancy icons)
379 :param feed: use output for feeds (no html and fancy icons)
438 """
380 """
439
381
440 action = user_log.action
382 action = user_log.action
441 action_params = ' '
383 action_params = ' '
442
384
443 x = action.split(':')
385 x = action.split(':')
444
386
445 if len(x) > 1:
387 if len(x) > 1:
446 action, action_params = x
388 action, action_params = x
447
389
448 def get_cs_links():
390 def get_cs_links():
449 revs_limit = 5 #display this amount always
391 revs_limit = 5 #display this amount always
450 revs_top_limit = 50 #show upto this amount of changesets hidden
392 revs_top_limit = 50 #show upto this amount of changesets hidden
451 revs = action_params.split(',')
393 revs = action_params.split(',')
452 repo_name = user_log.repository.repo_name
394 repo_name = user_log.repository.repo_name
453
395
454 from rhodecode.model.scm import ScmModel
396 from rhodecode.model.scm import ScmModel
455 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
397 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
456 invalidation_list=[])
398 invalidation_list=[])
457
399
458 message = lambda rev: get_changeset_safe(repo, rev).message
400 message = lambda rev: get_changeset_safe(repo, rev).message
459
401
460 cs_links = " " + ', '.join ([link_to(rev,
402 cs_links = " " + ', '.join ([link_to(rev,
461 url('changeset_home',
403 url('changeset_home',
462 repo_name=repo_name,
404 repo_name=repo_name,
463 revision=rev), title=tooltip(message(rev)),
405 revision=rev), title=tooltip(message(rev)),
464 class_='tooltip') for rev in revs[:revs_limit] ])
406 class_='tooltip') for rev in revs[:revs_limit] ])
465
407
466 compare_view = (' <div class="compare_view tooltip" title="%s">'
408 compare_view = (' <div class="compare_view tooltip" title="%s">'
467 '<a href="%s">%s</a> '
409 '<a href="%s">%s</a> '
468 '</div>' % (_('Show all combined changesets %s->%s' \
410 '</div>' % (_('Show all combined changesets %s->%s' \
469 % (revs[0], revs[-1])),
411 % (revs[0], revs[-1])),
470 url('changeset_home', repo_name=repo_name,
412 url('changeset_home', repo_name=repo_name,
471 revision='%s...%s' % (revs[0], revs[-1])
413 revision='%s...%s' % (revs[0], revs[-1])
472 ),
414 ),
473 _('compare view'))
415 _('compare view'))
474 )
416 )
475
417
476 if len(revs) > revs_limit:
418 if len(revs) > revs_limit:
477 uniq_id = revs[0]
419 uniq_id = revs[0]
478 html_tmpl = ('<span> %s '
420 html_tmpl = ('<span> %s '
479 '<a class="show_more" id="_%s" href="#more">%s</a> '
421 '<a class="show_more" id="_%s" href="#more">%s</a> '
480 '%s</span>')
422 '%s</span>')
481 if not feed:
423 if not feed:
482 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
424 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
483 % (len(revs) - revs_limit),
425 % (len(revs) - revs_limit),
484 _('revisions'))
426 _('revisions'))
485
427
486 if not feed:
428 if not feed:
487 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
429 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
488 else:
430 else:
489 html_tmpl = '<span id="%s"> %s </span>'
431 html_tmpl = '<span id="%s"> %s </span>'
490
432
491 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
433 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
492 url('changeset_home',
434 url('changeset_home',
493 repo_name=repo_name, revision=rev),
435 repo_name=repo_name, revision=rev),
494 title=message(rev), class_='tooltip')
436 title=message(rev), class_='tooltip')
495 for rev in revs[revs_limit:revs_top_limit]]))
437 for rev in revs[revs_limit:revs_top_limit]]))
496 if len(revs) > 1:
438 if len(revs) > 1:
497 cs_links += compare_view
439 cs_links += compare_view
498 return cs_links
440 return cs_links
499
441
500 def get_fork_name():
442 def get_fork_name():
501 repo_name = action_params
443 repo_name = action_params
502 return _('fork name ') + str(link_to(action_params, url('summary_home',
444 return _('fork name ') + str(link_to(action_params, url('summary_home',
503 repo_name=repo_name,)))
445 repo_name=repo_name,)))
504
446
505 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
447 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
506 'user_created_repo':(_('[created] repository'), None),
448 'user_created_repo':(_('[created] repository'), None),
507 'user_forked_repo':(_('[forked] repository'), get_fork_name),
449 'user_forked_repo':(_('[forked] repository'), get_fork_name),
508 'user_updated_repo':(_('[updated] repository'), None),
450 'user_updated_repo':(_('[updated] repository'), None),
509 'admin_deleted_repo':(_('[delete] repository'), None),
451 'admin_deleted_repo':(_('[delete] repository'), None),
510 'admin_created_repo':(_('[created] repository'), None),
452 'admin_created_repo':(_('[created] repository'), None),
511 'admin_forked_repo':(_('[forked] repository'), None),
453 'admin_forked_repo':(_('[forked] repository'), None),
512 'admin_updated_repo':(_('[updated] repository'), None),
454 'admin_updated_repo':(_('[updated] repository'), None),
513 'push':(_('[pushed] into'), get_cs_links),
455 'push':(_('[pushed] into'), get_cs_links),
514 'push_remote':(_('[pulled from remote] into'), get_cs_links),
456 'push_remote':(_('[pulled from remote] into'), get_cs_links),
515 'pull':(_('[pulled] from'), None),
457 'pull':(_('[pulled] from'), None),
516 'started_following_repo':(_('[started following] repository'), None),
458 'started_following_repo':(_('[started following] repository'), None),
517 'stopped_following_repo':(_('[stopped following] repository'), None),
459 'stopped_following_repo':(_('[stopped following] repository'), None),
518 }
460 }
519
461
520 action_str = action_map.get(action, action)
462 action_str = action_map.get(action, action)
521 if feed:
463 if feed:
522 action = action_str[0].replace('[', '').replace(']', '')
464 action = action_str[0].replace('[', '').replace(']', '')
523 else:
465 else:
524 action = action_str[0].replace('[', '<span class="journal_highlight">')\
466 action = action_str[0].replace('[', '<span class="journal_highlight">')\
525 .replace(']', '</span>')
467 .replace(']', '</span>')
526
468
527 action_params_func = lambda :""
469 action_params_func = lambda :""
528
470
529 if callable(action_str[1]):
471 if callable(action_str[1]):
530 action_params_func = action_str[1]
472 action_params_func = action_str[1]
531
473
532 return [literal(action), action_params_func]
474 return [literal(action), action_params_func]
533
475
534 def action_parser_icon(user_log):
476 def action_parser_icon(user_log):
535 action = user_log.action
477 action = user_log.action
536 action_params = None
478 action_params = None
537 x = action.split(':')
479 x = action.split(':')
538
480
539 if len(x) > 1:
481 if len(x) > 1:
540 action, action_params = x
482 action, action_params = x
541
483
542 tmpl = """<img src="%s%s" alt="%s"/>"""
484 tmpl = """<img src="%s%s" alt="%s"/>"""
543 map = {'user_deleted_repo':'database_delete.png',
485 map = {'user_deleted_repo':'database_delete.png',
544 'user_created_repo':'database_add.png',
486 'user_created_repo':'database_add.png',
545 'user_forked_repo':'arrow_divide.png',
487 'user_forked_repo':'arrow_divide.png',
546 'user_updated_repo':'database_edit.png',
488 'user_updated_repo':'database_edit.png',
547 'admin_deleted_repo':'database_delete.png',
489 'admin_deleted_repo':'database_delete.png',
548 'admin_created_repo':'database_add.png',
490 'admin_created_repo':'database_add.png',
549 'admin_forked_repo':'arrow_divide.png',
491 'admin_forked_repo':'arrow_divide.png',
550 'admin_updated_repo':'database_edit.png',
492 'admin_updated_repo':'database_edit.png',
551 'push':'script_add.png',
493 'push':'script_add.png',
552 'push_remote':'connect.png',
494 'push_remote':'connect.png',
553 'pull':'down_16.png',
495 'pull':'down_16.png',
554 'started_following_repo':'heart_add.png',
496 'started_following_repo':'heart_add.png',
555 'stopped_following_repo':'heart_delete.png',
497 'stopped_following_repo':'heart_delete.png',
556 }
498 }
557 return literal(tmpl % ((url('/images/icons/')),
499 return literal(tmpl % ((url('/images/icons/')),
558 map.get(action, action), action))
500 map.get(action, action), action))
559
501
560
502
561 #==============================================================================
503 #==============================================================================
562 # PERMS
504 # PERMS
563 #==============================================================================
505 #==============================================================================
564 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
506 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
565 HasRepoPermissionAny, HasRepoPermissionAll
507 HasRepoPermissionAny, HasRepoPermissionAll
566
508
567 #==============================================================================
509 #==============================================================================
568 # GRAVATAR URL
510 # GRAVATAR URL
569 #==============================================================================
511 #==============================================================================
570
512
571 def gravatar_url(email_address, size=30):
513 def gravatar_url(email_address, size=30):
572 if not str2bool(config['app_conf'].get('use_gravatar')):
514 if not str2bool(config['app_conf'].get('use_gravatar')):
573 return "/images/user%s.png" % size
515 return "/images/user%s.png" % size
574
516
575 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
517 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
576 default = 'identicon'
518 default = 'identicon'
577 baseurl_nossl = "http://www.gravatar.com/avatar/"
519 baseurl_nossl = "http://www.gravatar.com/avatar/"
578 baseurl_ssl = "https://secure.gravatar.com/avatar/"
520 baseurl_ssl = "https://secure.gravatar.com/avatar/"
579 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
521 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
580
522
581 if isinstance(email_address, unicode):
523 if isinstance(email_address, unicode):
582 #hashlib crashes on unicode items
524 #hashlib crashes on unicode items
583 email_address = email_address.encode('utf8', 'replace')
525 email_address = email_address.encode('utf8', 'replace')
584 # construct the url
526 # construct the url
585 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
527 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
586 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
528 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
587
529
588 return gravatar_url
530 return gravatar_url
589
531
590
532
591 #==============================================================================
533 #==============================================================================
592 # REPO PAGER
534 # REPO PAGER
593 #==============================================================================
535 #==============================================================================
594 class RepoPage(Page):
536 class RepoPage(Page):
595
537
596 def __init__(self, collection, page=1, items_per_page=20,
538 def __init__(self, collection, page=1, items_per_page=20,
597 item_count=None, url=None, branch_name=None, **kwargs):
539 item_count=None, url=None, branch_name=None, **kwargs):
598
540
599 """Create a "RepoPage" instance. special pager for paging
541 """Create a "RepoPage" instance. special pager for paging
600 repository
542 repository
601 """
543 """
602 self._url_generator = url
544 self._url_generator = url
603
545
604 # Safe the kwargs class-wide so they can be used in the pager() method
546 # Safe the kwargs class-wide so they can be used in the pager() method
605 self.kwargs = kwargs
547 self.kwargs = kwargs
606
548
607 # Save a reference to the collection
549 # Save a reference to the collection
608 self.original_collection = collection
550 self.original_collection = collection
609
551
610 self.collection = collection
552 self.collection = collection
611
553
612 # The self.page is the number of the current page.
554 # The self.page is the number of the current page.
613 # The first page has the number 1!
555 # The first page has the number 1!
614 try:
556 try:
615 self.page = int(page) # make it int() if we get it as a string
557 self.page = int(page) # make it int() if we get it as a string
616 except (ValueError, TypeError):
558 except (ValueError, TypeError):
617 self.page = 1
559 self.page = 1
618
560
619 self.items_per_page = items_per_page
561 self.items_per_page = items_per_page
620
562
621 # Unless the user tells us how many items the collections has
563 # Unless the user tells us how many items the collections has
622 # we calculate that ourselves.
564 # we calculate that ourselves.
623 if item_count is not None:
565 if item_count is not None:
624 self.item_count = item_count
566 self.item_count = item_count
625 else:
567 else:
626 self.item_count = len(self.collection)
568 self.item_count = len(self.collection)
627
569
628 # Compute the number of the first and last available page
570 # Compute the number of the first and last available page
629 if self.item_count > 0:
571 if self.item_count > 0:
630 self.first_page = 1
572 self.first_page = 1
631 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
573 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
632 self.last_page = self.first_page + self.page_count - 1
574 self.last_page = self.first_page + self.page_count - 1
633
575
634 # Make sure that the requested page number is the range of valid pages
576 # Make sure that the requested page number is the range of valid pages
635 if self.page > self.last_page:
577 if self.page > self.last_page:
636 self.page = self.last_page
578 self.page = self.last_page
637 elif self.page < self.first_page:
579 elif self.page < self.first_page:
638 self.page = self.first_page
580 self.page = self.first_page
639
581
640 # Note: the number of items on this page can be less than
582 # Note: the number of items on this page can be less than
641 # items_per_page if the last page is not full
583 # items_per_page if the last page is not full
642 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
584 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
643 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
585 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
644
586
645 iterator = self.collection.get_changesets(start=self.first_item,
587 iterator = self.collection.get_changesets(start=self.first_item,
646 end=self.last_item,
588 end=self.last_item,
647 reverse=True,
589 reverse=True,
648 branch_name=branch_name)
590 branch_name=branch_name)
649 self.items = list(iterator)
591 self.items = list(iterator)
650
592
651 # Links to previous and next page
593 # Links to previous and next page
652 if self.page > self.first_page:
594 if self.page > self.first_page:
653 self.previous_page = self.page - 1
595 self.previous_page = self.page - 1
654 else:
596 else:
655 self.previous_page = None
597 self.previous_page = None
656
598
657 if self.page < self.last_page:
599 if self.page < self.last_page:
658 self.next_page = self.page + 1
600 self.next_page = self.page + 1
659 else:
601 else:
660 self.next_page = None
602 self.next_page = None
661
603
662 # No items available
604 # No items available
663 else:
605 else:
664 self.first_page = None
606 self.first_page = None
665 self.page_count = 0
607 self.page_count = 0
666 self.last_page = None
608 self.last_page = None
667 self.first_item = None
609 self.first_item = None
668 self.last_item = None
610 self.last_item = None
669 self.previous_page = None
611 self.previous_page = None
670 self.next_page = None
612 self.next_page = None
671 self.items = []
613 self.items = []
672
614
673 # This is a subclass of the 'list' type. Initialise the list now.
615 # This is a subclass of the 'list' type. Initialise the list now.
674 list.__init__(self, self.items)
616 list.__init__(self, self.items)
675
617
676
618
677 def changed_tooltip(nodes):
619 def changed_tooltip(nodes):
678 if nodes:
620 if nodes:
679 pref = ': <br/> '
621 pref = ': <br/> '
680 suf = ''
622 suf = ''
681 if len(nodes) > 30:
623 if len(nodes) > 30:
682 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
624 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
683 return literal(pref + '<br/> '.join([safe_unicode(x.path) for x in nodes[:30]]) + suf)
625 return literal(pref + '<br/> '.join([safe_unicode(x.path) for x in nodes[:30]]) + suf)
684 else:
626 else:
685 return ': ' + _('No Files')
627 return ': ' + _('No Files')
686
628
687
629
688
630
689 def repo_link(groups_and_repos):
631 def repo_link(groups_and_repos):
690 groups, repo_name = groups_and_repos
632 groups, repo_name = groups_and_repos
691
633
692 if not groups:
634 if not groups:
693 return repo_name
635 return repo_name
694 else:
636 else:
695 def make_link(group):
637 def make_link(group):
696 return link_to(group.group_name, url('repos_group', id=group.group_id))
638 return link_to(group.group_name, url('repos_group', id=group.group_id))
697 return literal(' &raquo; '.join(map(make_link, groups)) + \
639 return literal(' &raquo; '.join(map(make_link, groups)) + \
698 " &raquo; " + repo_name)
640 " &raquo; " + repo_name)
699
641
700
642
General Comments 0
You need to be logged in to leave comments. Login now