##// END OF EJS Templates
added icons to journal, extend show more to actually show more pushed revisions,
marcink -
r808:1af15d66 beta
parent child Browse files
Show More
@@ -1,512 +1,546 b''
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 from pygments.formatters import HtmlFormatter
8 from pygments.formatters import HtmlFormatter
9 from pygments import highlight as code_highlight
9 from pygments import highlight as code_highlight
10 from pylons import url, app_globals as g
10 from pylons import url, app_globals as g
11 from pylons.i18n.translation import _, ungettext
11 from pylons.i18n.translation import _, ungettext
12 from vcs.utils.annotate import annotate_highlight
12 from vcs.utils.annotate import annotate_highlight
13 from webhelpers.html import literal, HTML, escape
13 from webhelpers.html import literal, HTML, escape
14 from webhelpers.html.tools import *
14 from webhelpers.html.tools import *
15 from webhelpers.html.builder import make_tag
15 from webhelpers.html.builder import make_tag
16 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
16 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
17 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
17 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
18 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
18 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
19 password, textarea, title, ul, xml_declaration, radio
19 password, textarea, title, ul, xml_declaration, radio
20 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
20 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
21 mail_to, strip_links, strip_tags, tag_re
21 mail_to, strip_links, strip_tags, tag_re
22 from webhelpers.number import format_byte_size, format_bit_size
22 from webhelpers.number import format_byte_size, format_bit_size
23 from webhelpers.pylonslib import Flash as _Flash
23 from webhelpers.pylonslib import Flash as _Flash
24 from webhelpers.pylonslib.secure_form import secure_form
24 from webhelpers.pylonslib.secure_form import secure_form
25 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
25 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
26 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
26 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
27 replace_whitespace, urlify, truncate, wrap_paragraphs
27 replace_whitespace, urlify, truncate, wrap_paragraphs
28 from webhelpers.date import time_ago_in_words
28 from webhelpers.date import time_ago_in_words
29
29
30 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
30 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
31 convert_boolean_attrs, NotGiven
31 convert_boolean_attrs, NotGiven
32
32
33 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
33 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
34 _set_input_attrs(attrs, type, name, value)
34 _set_input_attrs(attrs, type, name, value)
35 _set_id_attr(attrs, id, name)
35 _set_id_attr(attrs, id, name)
36 convert_boolean_attrs(attrs, ["disabled"])
36 convert_boolean_attrs(attrs, ["disabled"])
37 return HTML.input(**attrs)
37 return HTML.input(**attrs)
38
38
39 reset = _reset
39 reset = _reset
40
40
41
41
42 def get_token():
42 def get_token():
43 """Return the current authentication token, creating one if one doesn't
43 """Return the current authentication token, creating one if one doesn't
44 already exist.
44 already exist.
45 """
45 """
46 token_key = "_authentication_token"
46 token_key = "_authentication_token"
47 from pylons import session
47 from pylons import session
48 if not token_key in session:
48 if not token_key in session:
49 try:
49 try:
50 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
50 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
51 except AttributeError: # Python < 2.4
51 except AttributeError: # Python < 2.4
52 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
52 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
53 session[token_key] = token
53 session[token_key] = token
54 if hasattr(session, 'save'):
54 if hasattr(session, 'save'):
55 session.save()
55 session.save()
56 return session[token_key]
56 return session[token_key]
57
57
58
58
59 #Custom helpers here :)
59 #Custom helpers here :)
60 class _Link(object):
60 class _Link(object):
61 '''
61 '''
62 Make a url based on label and url with help of url_for
62 Make a url based on label and url with help of url_for
63 :param label:name of link if not defined url is used
63 :param label:name of link if not defined url is used
64 :param url: the url for link
64 :param url: the url for link
65 '''
65 '''
66
66
67 def __call__(self, label='', *url_, **urlargs):
67 def __call__(self, label='', *url_, **urlargs):
68 if label is None or '':
68 if label is None or '':
69 label = url
69 label = url
70 link_fn = link_to(label, url(*url_, **urlargs))
70 link_fn = link_to(label, url(*url_, **urlargs))
71 return link_fn
71 return link_fn
72
72
73 link = _Link()
73 link = _Link()
74
74
75 class _GetError(object):
75 class _GetError(object):
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 def recursive_replace(str, replace=' '):
84 def recursive_replace(str, replace=' '):
85 """
85 """
86 Recursive replace of given sign to just one instance
86 Recursive replace of given sign to just one instance
87 :param str: given string
87 :param str: given string
88 :param replace:char to find and replace multiple instances
88 :param replace:char to find and replace multiple instances
89
89
90 Examples::
90 Examples::
91 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
91 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
92 'Mighty-Mighty-Bo-sstones'
92 'Mighty-Mighty-Bo-sstones'
93 """
93 """
94
94
95 if str.find(replace * 2) == -1:
95 if str.find(replace * 2) == -1:
96 return str
96 return str
97 else:
97 else:
98 str = str.replace(replace * 2, replace)
98 str = str.replace(replace * 2, replace)
99 return recursive_replace(str, replace)
99 return recursive_replace(str, replace)
100
100
101 class _ToolTip(object):
101 class _ToolTip(object):
102
102
103 def __call__(self, tooltip_title, trim_at=50):
103 def __call__(self, tooltip_title, trim_at=50):
104 """
104 """
105 Special function just to wrap our text into nice formatted autowrapped
105 Special function just to wrap our text into nice formatted autowrapped
106 text
106 text
107 :param tooltip_title:
107 :param tooltip_title:
108 """
108 """
109
109
110 return wrap_paragraphs(escape(tooltip_title), trim_at)\
110 return wrap_paragraphs(escape(tooltip_title), trim_at)\
111 .replace('\n', '<br/>')
111 .replace('\n', '<br/>')
112
112
113 def activate(self):
113 def activate(self):
114 """
114 """
115 Adds tooltip mechanism to the given Html all tooltips have to have
115 Adds tooltip mechanism to the given Html all tooltips have to have
116 set class tooltip and set attribute tooltip_title.
116 set class tooltip and set attribute tooltip_title.
117 Then a tooltip will be generated based on that
117 Then a tooltip will be generated based on that
118 All with yui js tooltip
118 All with yui js tooltip
119 """
119 """
120
120
121 js = '''
121 js = '''
122 YAHOO.util.Event.onDOMReady(function(){
122 YAHOO.util.Event.onDOMReady(function(){
123 function toolTipsId(){
123 function toolTipsId(){
124 var ids = [];
124 var ids = [];
125 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
125 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
126
126
127 for (var i = 0; i < tts.length; i++) {
127 for (var i = 0; i < tts.length; i++) {
128 //if element doesn't not have and id autgenerate one for tooltip
128 //if element doesn't not have and id autgenerate one for tooltip
129
129
130 if (!tts[i].id){
130 if (!tts[i].id){
131 tts[i].id='tt'+i*100;
131 tts[i].id='tt'+i*100;
132 }
132 }
133 ids.push(tts[i].id);
133 ids.push(tts[i].id);
134 }
134 }
135 return ids
135 return ids
136 };
136 };
137 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
137 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
138 context: toolTipsId(),
138 context: toolTipsId(),
139 monitorresize:false,
139 monitorresize:false,
140 xyoffset :[0,0],
140 xyoffset :[0,0],
141 autodismissdelay:300000,
141 autodismissdelay:300000,
142 hidedelay:5,
142 hidedelay:5,
143 showdelay:20,
143 showdelay:20,
144 });
144 });
145
145
146 //Mouse Over event disabled for new repositories since they don't
146 //Mouse Over event disabled for new repositories since they don't
147 //have last commit message
147 //have last commit message
148 myToolTips.contextMouseOverEvent.subscribe(
148 myToolTips.contextMouseOverEvent.subscribe(
149 function(type, args) {
149 function(type, args) {
150 var context = args[0];
150 var context = args[0];
151 var txt = context.getAttribute('tooltip_title');
151 var txt = context.getAttribute('tooltip_title');
152 if(txt){
152 if(txt){
153 return true;
153 return true;
154 }
154 }
155 else{
155 else{
156 return false;
156 return false;
157 }
157 }
158 });
158 });
159
159
160
160
161 // Set the text for the tooltip just before we display it. Lazy method
161 // Set the text for the tooltip just before we display it. Lazy method
162 myToolTips.contextTriggerEvent.subscribe(
162 myToolTips.contextTriggerEvent.subscribe(
163 function(type, args) {
163 function(type, args) {
164
164
165
165
166 var context = args[0];
166 var context = args[0];
167
167
168 var txt = context.getAttribute('tooltip_title');
168 var txt = context.getAttribute('tooltip_title');
169 this.cfg.setProperty("text", txt);
169 this.cfg.setProperty("text", txt);
170
170
171
171
172 // positioning of tooltip
172 // positioning of tooltip
173 var tt_w = this.element.clientWidth;
173 var tt_w = this.element.clientWidth;
174 var tt_h = this.element.clientHeight;
174 var tt_h = this.element.clientHeight;
175
175
176 var context_w = context.offsetWidth;
176 var context_w = context.offsetWidth;
177 var context_h = context.offsetHeight;
177 var context_h = context.offsetHeight;
178
178
179 var pos_x = YAHOO.util.Dom.getX(context);
179 var pos_x = YAHOO.util.Dom.getX(context);
180 var pos_y = YAHOO.util.Dom.getY(context);
180 var pos_y = YAHOO.util.Dom.getY(context);
181
181
182 var display_strategy = 'top';
182 var display_strategy = 'top';
183 var xy_pos = [0,0];
183 var xy_pos = [0,0];
184 switch (display_strategy){
184 switch (display_strategy){
185
185
186 case 'top':
186 case 'top':
187 var cur_x = (pos_x+context_w/2)-(tt_w/2);
187 var cur_x = (pos_x+context_w/2)-(tt_w/2);
188 var cur_y = pos_y-tt_h-4;
188 var cur_y = pos_y-tt_h-4;
189 xy_pos = [cur_x,cur_y];
189 xy_pos = [cur_x,cur_y];
190 break;
190 break;
191 case 'bottom':
191 case 'bottom':
192 var cur_x = (pos_x+context_w/2)-(tt_w/2);
192 var cur_x = (pos_x+context_w/2)-(tt_w/2);
193 var cur_y = pos_y+context_h+4;
193 var cur_y = pos_y+context_h+4;
194 xy_pos = [cur_x,cur_y];
194 xy_pos = [cur_x,cur_y];
195 break;
195 break;
196 case 'left':
196 case 'left':
197 var cur_x = (pos_x-tt_w-4);
197 var cur_x = (pos_x-tt_w-4);
198 var cur_y = pos_y-((tt_h/2)-context_h/2);
198 var cur_y = pos_y-((tt_h/2)-context_h/2);
199 xy_pos = [cur_x,cur_y];
199 xy_pos = [cur_x,cur_y];
200 break;
200 break;
201 case 'right':
201 case 'right':
202 var cur_x = (pos_x+context_w+4);
202 var cur_x = (pos_x+context_w+4);
203 var cur_y = pos_y-((tt_h/2)-context_h/2);
203 var cur_y = pos_y-((tt_h/2)-context_h/2);
204 xy_pos = [cur_x,cur_y];
204 xy_pos = [cur_x,cur_y];
205 break;
205 break;
206 default:
206 default:
207 var cur_x = (pos_x+context_w/2)-(tt_w/2);
207 var cur_x = (pos_x+context_w/2)-(tt_w/2);
208 var cur_y = pos_y-tt_h-4;
208 var cur_y = pos_y-tt_h-4;
209 xy_pos = [cur_x,cur_y];
209 xy_pos = [cur_x,cur_y];
210 break;
210 break;
211
211
212 }
212 }
213
213
214 this.cfg.setProperty("xy",xy_pos);
214 this.cfg.setProperty("xy",xy_pos);
215
215
216 });
216 });
217
217
218 //Mouse out
218 //Mouse out
219 myToolTips.contextMouseOutEvent.subscribe(
219 myToolTips.contextMouseOutEvent.subscribe(
220 function(type, args) {
220 function(type, args) {
221 var context = args[0];
221 var context = args[0];
222
222
223 });
223 });
224 });
224 });
225 '''
225 '''
226 return literal(js)
226 return literal(js)
227
227
228 tooltip = _ToolTip()
228 tooltip = _ToolTip()
229
229
230 class _FilesBreadCrumbs(object):
230 class _FilesBreadCrumbs(object):
231
231
232 def __call__(self, repo_name, rev, paths):
232 def __call__(self, repo_name, rev, paths):
233 url_l = [link_to(repo_name, url('files_home',
233 url_l = [link_to(repo_name, url('files_home',
234 repo_name=repo_name,
234 repo_name=repo_name,
235 revision=rev, f_path=''))]
235 revision=rev, f_path=''))]
236 paths_l = paths.split('/')
236 paths_l = paths.split('/')
237
237
238 for cnt, p in enumerate(paths_l):
238 for cnt, p in enumerate(paths_l):
239 if p != '':
239 if p != '':
240 url_l.append(link_to(p, url('files_home',
240 url_l.append(link_to(p, url('files_home',
241 repo_name=repo_name,
241 repo_name=repo_name,
242 revision=rev,
242 revision=rev,
243 f_path='/'.join(paths_l[:cnt + 1]))))
243 f_path='/'.join(paths_l[:cnt + 1]))))
244
244
245 return literal('/'.join(url_l))
245 return literal('/'.join(url_l))
246
246
247 files_breadcrumbs = _FilesBreadCrumbs()
247 files_breadcrumbs = _FilesBreadCrumbs()
248 class CodeHtmlFormatter(HtmlFormatter):
248 class CodeHtmlFormatter(HtmlFormatter):
249
249
250 def wrap(self, source, outfile):
250 def wrap(self, source, outfile):
251 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
251 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
252
252
253 def _wrap_code(self, source):
253 def _wrap_code(self, source):
254 for cnt, it in enumerate(source):
254 for cnt, it in enumerate(source):
255 i, t = it
255 i, t = it
256 t = '<div id="#S-%s">%s</div>' % (cnt + 1, t)
256 t = '<div id="#S-%s">%s</div>' % (cnt + 1, t)
257 yield i, t
257 yield i, t
258 def pygmentize(filenode, **kwargs):
258 def pygmentize(filenode, **kwargs):
259 """
259 """
260 pygmentize function using pygments
260 pygmentize function using pygments
261 :param filenode:
261 :param filenode:
262 """
262 """
263 return literal(code_highlight(filenode.content,
263 return literal(code_highlight(filenode.content,
264 filenode.lexer, CodeHtmlFormatter(**kwargs)))
264 filenode.lexer, CodeHtmlFormatter(**kwargs)))
265
265
266 def pygmentize_annotation(filenode, **kwargs):
266 def pygmentize_annotation(filenode, **kwargs):
267 """
267 """
268 pygmentize function for annotation
268 pygmentize function for annotation
269 :param filenode:
269 :param filenode:
270 """
270 """
271
271
272 color_dict = {}
272 color_dict = {}
273 def gen_color():
273 def gen_color():
274 """generator for getting 10k of evenly distibuted colors using hsv color
274 """generator for getting 10k of evenly distibuted colors using hsv color
275 and golden ratio.
275 and golden ratio.
276 """
276 """
277 import colorsys
277 import colorsys
278 n = 10000
278 n = 10000
279 golden_ratio = 0.618033988749895
279 golden_ratio = 0.618033988749895
280 h = 0.22717784590367374
280 h = 0.22717784590367374
281 #generate 10k nice web friendly colors in the same order
281 #generate 10k nice web friendly colors in the same order
282 for c in xrange(n):
282 for c in xrange(n):
283 h += golden_ratio
283 h += golden_ratio
284 h %= 1
284 h %= 1
285 HSV_tuple = [h, 0.95, 0.95]
285 HSV_tuple = [h, 0.95, 0.95]
286 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
286 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
287 yield map(lambda x:str(int(x * 256)), RGB_tuple)
287 yield map(lambda x:str(int(x * 256)), RGB_tuple)
288
288
289 cgenerator = gen_color()
289 cgenerator = gen_color()
290
290
291 def get_color_string(cs):
291 def get_color_string(cs):
292 if color_dict.has_key(cs):
292 if color_dict.has_key(cs):
293 col = color_dict[cs]
293 col = color_dict[cs]
294 else:
294 else:
295 col = color_dict[cs] = cgenerator.next()
295 col = color_dict[cs] = cgenerator.next()
296 return "color: rgb(%s)! important;" % (', '.join(col))
296 return "color: rgb(%s)! important;" % (', '.join(col))
297
297
298 def url_func(changeset):
298 def url_func(changeset):
299 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
299 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
300 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
300 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
301
301
302 tooltip_html = tooltip_html % (changeset.author,
302 tooltip_html = tooltip_html % (changeset.author,
303 changeset.date,
303 changeset.date,
304 tooltip(changeset.message))
304 tooltip(changeset.message))
305 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
305 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
306 short_id(changeset.raw_id))
306 short_id(changeset.raw_id))
307 uri = link_to(
307 uri = link_to(
308 lnk_format,
308 lnk_format,
309 url('changeset_home', repo_name=changeset.repository.name,
309 url('changeset_home', repo_name=changeset.repository.name,
310 revision=changeset.raw_id),
310 revision=changeset.raw_id),
311 style=get_color_string(changeset.raw_id),
311 style=get_color_string(changeset.raw_id),
312 class_='tooltip',
312 class_='tooltip',
313 tooltip_title=tooltip_html
313 tooltip_title=tooltip_html
314 )
314 )
315
315
316 uri += '\n'
316 uri += '\n'
317 return uri
317 return uri
318 return literal(annotate_highlight(filenode, url_func, **kwargs))
318 return literal(annotate_highlight(filenode, url_func, **kwargs))
319
319
320 def repo_name_slug(value):
320 def repo_name_slug(value):
321 """Return slug of name of repository
321 """Return slug of name of repository
322 This function is called on each creation/modification
322 This function is called on each creation/modification
323 of repository to prevent bad names in repo
323 of repository to prevent bad names in repo
324 """
324 """
325 slug = remove_formatting(value)
325 slug = remove_formatting(value)
326 slug = strip_tags(slug)
326 slug = strip_tags(slug)
327
327
328 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
328 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
329 slug = slug.replace(c, '-')
329 slug = slug.replace(c, '-')
330 slug = recursive_replace(slug, '-')
330 slug = recursive_replace(slug, '-')
331 slug = collapse(slug, '-')
331 slug = collapse(slug, '-')
332 return slug
332 return slug
333
333
334 def get_changeset_safe(repo, rev):
334 def get_changeset_safe(repo, rev):
335 from vcs.backends.base import BaseRepository
335 from vcs.backends.base import BaseRepository
336 from vcs.exceptions import RepositoryError
336 from vcs.exceptions import RepositoryError
337 if not isinstance(repo, BaseRepository):
337 if not isinstance(repo, BaseRepository):
338 raise Exception('You must pass an Repository '
338 raise Exception('You must pass an Repository '
339 'object as first argument got %s', type(repo))
339 'object as first argument got %s', type(repo))
340
340
341 try:
341 try:
342 cs = repo.get_changeset(rev)
342 cs = repo.get_changeset(rev)
343 except RepositoryError:
343 except RepositoryError:
344 from rhodecode.lib.utils import EmptyChangeset
344 from rhodecode.lib.utils import EmptyChangeset
345 cs = EmptyChangeset()
345 cs = EmptyChangeset()
346 return cs
346 return cs
347
347
348
348
349 flash = _Flash()
349 flash = _Flash()
350
350
351
351
352 #==============================================================================
352 #==============================================================================
353 # MERCURIAL FILTERS available via h.
353 # MERCURIAL FILTERS available via h.
354 #==============================================================================
354 #==============================================================================
355 from mercurial import util
355 from mercurial import util
356 from mercurial.templatefilters import person as _person
356 from mercurial.templatefilters import person as _person
357
357
358
358
359
359
360 def _age(curdate):
360 def _age(curdate):
361 """turns a datetime into an age string."""
361 """turns a datetime into an age string."""
362
362
363 if not curdate:
363 if not curdate:
364 return ''
364 return ''
365
365
366 from datetime import timedelta, datetime
366 from datetime import timedelta, datetime
367
367
368 agescales = [("year", 3600 * 24 * 365),
368 agescales = [("year", 3600 * 24 * 365),
369 ("month", 3600 * 24 * 30),
369 ("month", 3600 * 24 * 30),
370 ("day", 3600 * 24),
370 ("day", 3600 * 24),
371 ("hour", 3600),
371 ("hour", 3600),
372 ("minute", 60),
372 ("minute", 60),
373 ("second", 1), ]
373 ("second", 1), ]
374
374
375 age = datetime.now() - curdate
375 age = datetime.now() - curdate
376 age_seconds = (age.days * agescales[2][1]) + age.seconds
376 age_seconds = (age.days * agescales[2][1]) + age.seconds
377 pos = 1
377 pos = 1
378 for scale in agescales:
378 for scale in agescales:
379 if scale[1] <= age_seconds:
379 if scale[1] <= age_seconds:
380 if pos == 6:pos = 5
380 if pos == 6:pos = 5
381 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
381 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
382 pos += 1
382 pos += 1
383
383
384 return _('just now')
384 return _('just now')
385
385
386 age = lambda x:_age(x)
386 age = lambda x:_age(x)
387 capitalize = lambda x: x.capitalize()
387 capitalize = lambda x: x.capitalize()
388 email = util.email
388 email = util.email
389 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
389 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
390 person = lambda x: _person(x)
390 person = lambda x: _person(x)
391 short_id = lambda x: x[:12]
391 short_id = lambda x: x[:12]
392
392
393
393
394 def bool2icon(value):
394 def bool2icon(value):
395 """
395 """
396 Returns True/False values represented as small html image of true/false
396 Returns True/False values represented as small html image of true/false
397 icons
397 icons
398 :param value: bool value
398 :param value: bool value
399 """
399 """
400
400
401 if value is True:
401 if value is True:
402 return HTML.tag('img', src="/images/icons/accept.png", alt=_('True'))
402 return HTML.tag('img', src="/images/icons/accept.png", alt=_('True'))
403
403
404 if value is False:
404 if value is False:
405 return HTML.tag('img', src="/images/icons/cancel.png", alt=_('False'))
405 return HTML.tag('img', src="/images/icons/cancel.png", alt=_('False'))
406
406
407 return value
407 return value
408
408
409
409
410 def action_parser(user_log):
410 def action_parser(user_log):
411 """
411 """
412 This helper will map the specified string action into translated
412 This helper will map the specified string action into translated
413 fancy names with icons and links
413 fancy names with icons and links
414
414
415 @param action:
415 @param action:
416 """
416 """
417 action = user_log.action
417 action = user_log.action
418 action_params = None
418 action_params = None
419
419
420 x = action.split(':')
420 x = action.split(':')
421
421
422 if len(x) > 1:
422 if len(x) > 1:
423 action, action_params = x
423 action, action_params = x
424
424
425 def get_cs_links():
425 def get_cs_links():
426 if action == 'push':
426 if action == 'push':
427 revs_limit = 5
427 revs_limit = 5
428 revs = action_params.split(',')
428 revs = action_params.split(',')
429 cs_links = " " + ', '.join ([link(rev,
429 cs_links = " " + ', '.join ([link(rev,
430 url('changeset_home',
430 url('changeset_home',
431 repo_name=user_log.repository.repo_name,
431 repo_name=user_log.repository.repo_name,
432 revision=rev)) for rev in revs[:revs_limit] ])
432 revision=rev)) for rev in revs[:revs_limit] ])
433 if len(revs) > revs_limit:
433 if len(revs) > revs_limit:
434 html_tmpl = '<span title="%s"> %s </span>'
434 uniq_id = revs[0]
435 cs_links += html_tmpl % (', '.join(r for r in revs[revs_limit:]),
435 html_tmpl = ('<span> %s '
436 _('and %s more revisions') \
436 '<a class="show_more" id="_%s" href="#">%s</a> '
437 % (len(revs) - revs_limit))
437 '%s</span>')
438 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
439 % (len(revs) - revs_limit),
440 _('revisions'))
441
442 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
443 cs_links += html_tmpl % (uniq_id, ', '.join([link(rev,
444 url('changeset_home',
445 repo_name=user_log.repository.repo_name,
446 revision=rev)) for rev in revs[:revs_limit] ]))
438
447
439 return cs_links
448 return cs_links
440 return ''
449 return ''
441
450
442 def get_fork_name():
451 def get_fork_name():
443 if action == 'user_forked_repo':
452 if action == 'user_forked_repo':
444 from rhodecode.model.scm import ScmModel
453 from rhodecode.model.scm import ScmModel
445 repo_name = action_params
454 repo_name = action_params
446 repo = ScmModel().get(repo_name)
455 repo = ScmModel().get(repo_name)
447 if repo is None:
456 if repo is None:
448 return repo_name
457 return repo_name
449 return link_to(action_params, url('summary_home',
458 return link_to(action_params, url('summary_home',
450 repo_name=repo.name,),
459 repo_name=repo.name,),
451 title=repo.dbrepo.description)
460 title=repo.dbrepo.description)
452 return ''
461 return ''
453 map = {'user_deleted_repo':_('User [deleted] repository'),
462 map = {'user_deleted_repo':_('User [deleted] repository'),
454 'user_created_repo':_('User [created] repository'),
463 'user_created_repo':_('User [created] repository'),
455 'user_forked_repo':_('User [forked] repository as: %s') % get_fork_name(),
464 'user_forked_repo':_('User [forked] repository as: %s') % get_fork_name(),
456 'user_updated_repo':_('User [updated] repository'),
465 'user_updated_repo':_('User [updated] repository'),
457 'admin_deleted_repo':_('Admin [delete] repository'),
466 'admin_deleted_repo':_('Admin [delete] repository'),
458 'admin_created_repo':_('Admin [created] repository'),
467 'admin_created_repo':_('Admin [created] repository'),
459 'admin_forked_repo':_('Admin [forked] repository'),
468 'admin_forked_repo':_('Admin [forked] repository'),
460 'admin_updated_repo':_('Admin [updated] repository'),
469 'admin_updated_repo':_('Admin [updated] repository'),
461 'push':_('[Pushed] %s') % get_cs_links(),
470 'push':_('[Pushed] %s') % get_cs_links(),
462 'pull':_('[Pulled]'),
471 'pull':_('[Pulled]'),
463 'started_following_repo':_('User [started following] repository'),
472 'started_following_repo':_('User [started following] repository'),
464 'stopped_following_repo':_('User [stopped following] repository'),
473 'stopped_following_repo':_('User [stopped following] repository'),
465 }
474 }
466
475
467 action_str = map.get(action, action)
476 action_str = map.get(action, action)
468 return literal(action_str.replace('[', '<span class="journal_highlight">').replace(']', '</span>'))
477 return literal(action_str.replace('[', '<span class="journal_highlight">')\
478 .replace(']', '</span>'))
479
480 def action_parser_icon(user_log):
481 action = user_log.action
482 action_params = None
483 x = action.split(':')
484
485 if len(x) > 1:
486 action, action_params = x
487
488 tmpl = """<img src="/images/icons/%s">"""
489 map = {'user_deleted_repo':'database_delete.png',
490 'user_created_repo':'database_add.png',
491 'user_forked_repo':'arrow_divide.png',
492 'user_updated_repo':'database_edit.png',
493 'admin_deleted_repo':'database_delete.png',
494 'admin_created_repo':'database_ddd.png',
495 'admin_forked_repo':'arrow_divide.png',
496 'admin_updated_repo':'database_edit.png',
497 'push':'script_add.png',
498 'pull':'down_16.png',
499 'started_following_repo':'heart_add.png',
500 'stopped_following_repo':'heart_delete.png',
501 }
502 return literal(tmpl % map.get(action, action))
469
503
470
504
471 #==============================================================================
505 #==============================================================================
472 # PERMS
506 # PERMS
473 #==============================================================================
507 #==============================================================================
474 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
508 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
475 HasRepoPermissionAny, HasRepoPermissionAll
509 HasRepoPermissionAny, HasRepoPermissionAll
476
510
477 #==============================================================================
511 #==============================================================================
478 # GRAVATAR URL
512 # GRAVATAR URL
479 #==============================================================================
513 #==============================================================================
480 import hashlib
514 import hashlib
481 import urllib
515 import urllib
482 from pylons import request
516 from pylons import request
483
517
484 def gravatar_url(email_address, size=30):
518 def gravatar_url(email_address, size=30):
485 ssl_enabled = 'https' == request.environ.get('HTTP_X_URL_SCHEME')
519 ssl_enabled = 'https' == request.environ.get('HTTP_X_URL_SCHEME')
486 default = 'identicon'
520 default = 'identicon'
487 baseurl_nossl = "http://www.gravatar.com/avatar/"
521 baseurl_nossl = "http://www.gravatar.com/avatar/"
488 baseurl_ssl = "https://secure.gravatar.com/avatar/"
522 baseurl_ssl = "https://secure.gravatar.com/avatar/"
489 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
523 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
490
524
491
525
492 # construct the url
526 # construct the url
493 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
527 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
494 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
528 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
495
529
496 return gravatar_url
530 return gravatar_url
497
531
498 def safe_unicode(str):
532 def safe_unicode(str):
499 """safe unicode function. In case of UnicodeDecode error we try to return
533 """safe unicode function. In case of UnicodeDecode error we try to return
500 unicode with errors replace, if this failes we return unicode with
534 unicode with errors replace, if this failes we return unicode with
501 string_escape decoding """
535 string_escape decoding """
502
536
503 try:
537 try:
504 u_str = unicode(str)
538 u_str = unicode(str)
505 except UnicodeDecodeError:
539 except UnicodeDecodeError:
506 try:
540 try:
507 u_str = unicode(str, 'utf-8', 'replace')
541 u_str = unicode(str, 'utf-8', 'replace')
508 except UnicodeDecodeError:
542 except UnicodeDecodeError:
509 #incase we have a decode error just represent as byte string
543 #incase we have a decode error just represent as byte string
510 u_str = unicode(str(str).encode('string_escape'))
544 u_str = unicode(str(str).encode('string_escape'))
511
545
512 return u_str
546 return u_str
@@ -1,48 +1,63 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 %if c.users_log:
2 %if c.users_log:
3 <table>
3 <table>
4 <tr>
4 <tr>
5 <th class="left">${_('Username')}</th>
5 <th class="left">${_('Username')}</th>
6 <th class="left">${_('Action')}</th>
6 <th class="left">${_('Action')}</th>
7 <th class="left">${_('Repository')}</th>
7 <th class="left">${_('Repository')}</th>
8 <th class="left">${_('Date')}</th>
8 <th class="left">${_('Date')}</th>
9 <th class="left">${_('From IP')}</th>
9 <th class="left">${_('From IP')}</th>
10 </tr>
10 </tr>
11
11
12 %for cnt,l in enumerate(c.users_log):
12 %for cnt,l in enumerate(c.users_log):
13 <tr class="parity${cnt%2}">
13 <tr class="parity${cnt%2}">
14 <td>${h.link_to(l.user.username,h.url('edit_user', id=l.user.user_id))}</td>
14 <td>${h.link_to(l.user.username,h.url('edit_user', id=l.user.user_id))}</td>
15 <td>${h.action_parser(l)}</td>
15 <td>${h.action_parser(l)}</td>
16 <td>
16 <td>
17 %if l.repository:
17 %if l.repository:
18 ${h.link_to(l.repository.repo_name,h.url('summary_home',repo_name=l.repository.repo_name))}
18 ${h.link_to(l.repository.repo_name,h.url('summary_home',repo_name=l.repository.repo_name))}
19 %else:
19 %else:
20 ${l.repository_name}
20 ${l.repository_name}
21 %endif
21 %endif
22 </td>
22 </td>
23
23
24 <td>${l.action_date}</td>
24 <td>${l.action_date}</td>
25 <td>${l.user_ip}</td>
25 <td>${l.user_ip}</td>
26 </tr>
26 </tr>
27 %endfor
27 %endfor
28 </table>
28 </table>
29
29
30 <script type="text/javascript">
30 <script type="text/javascript">
31 var data_div = 'user_log';
31 var data_div = 'user_log';
32 YAHOO.util.Event.onDOMReady(function(){
32 YUE.onDOMReady(function(){
33 YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){
33 YUE.on(YUD.getElementsByClassName('pager_link'),"click",function(){
34 YAHOO.util.Dom.setStyle('shortlog_data','opacity','0.3');});});
34 YUD.setStyle(data_div,'opacity','0.3');});
35 YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
36 var el = e.target;
37 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
38 YUD.setStyle(el.parentNode,'display','none');
39 });
40 });
35 </script>
41 </script>
36
42
37
43
38 <div class="pagination-wh pagination-left">
44 <div class="pagination-wh pagination-left">
39 ${c.users_log.pager('$link_previous ~2~ $link_next',
45 ${c.users_log.pager('$link_previous ~2~ $link_next',
40 onclick="""YAHOO.util.Connect.asyncRequest('GET','$partial_url',{
46 onclick="""YAHOO.util.Connect.asyncRequest('GET','$partial_url',{
41 success:function(o){YAHOO.util.Dom.get(data_div).innerHTML=o.responseText;
47 success:function(o){
42 YAHOO.util.Event.addListener(YAHOO.util.Dom.getElementsByClassName('pager_link'),"click",function(){
48 YUD.get(data_div).innerHTML=o.responseText;
43 YAHOO.util.Dom.setStyle(data_div,'opacity','0.3');});
49 YUE.on(YUD.getElementsByClassName('pager_link'),"click",function(){
44 YAHOO.util.Dom.setStyle(data_div,'opacity','1');}},null); return false;""")}
50 YUD.setStyle(data_div,'opacity','0.3');
51 });
52 YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
53 var el = e.target;
54 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
55 YUD.setStyle(el.parentNode,'display','none');
56 });
57 YUD.setStyle(data_div,'opacity','1');}
58
59 },null); return false;""")}
45 </div>
60 </div>
46 %else:
61 %else:
47 ${_('No actions yet')}
62 ${_('No actions yet')}
48 %endif
63 %endif
@@ -1,79 +1,92 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="base/base.html"/>
2 <%inherit file="base/base.html"/>
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Journal')} - ${c.rhodecode_name}
4 ${_('Journal')} - ${c.rhodecode_name}
5 </%def>
5 </%def>
6 <%def name="breadcrumbs()">
6 <%def name="breadcrumbs()">
7 ${c.rhodecode_name}
7 ${c.rhodecode_name}
8 </%def>
8 </%def>
9 <%def name="page_nav()">
9 <%def name="page_nav()">
10 ${self.menu('home')}
10 ${self.menu('home')}
11 </%def>
11 </%def>
12 <%def name="main()">
12 <%def name="main()">
13
13
14 <div class="box box-left">
14 <div class="box box-left">
15 <!-- box / title -->
15 <!-- box / title -->
16 <div class="title">
16 <div class="title">
17 <h5>${_('Journal')}</h5>
17 <h5>${_('Journal')}</h5>
18 </div>
18 </div>
19 <div>
19 <div>
20 %if c.journal:
20 %if c.journal:
21 %for entry in c.journal:
21 %for entry in c.journal:
22 <div style="padding:10px">
22 <div style="padding:10px">
23 <div class="gravatar">
23 <div class="gravatar">
24 <img alt="gravatar" src="${h.gravatar_url(entry.user.email)}"/>
24 <img alt="gravatar" src="${h.gravatar_url(entry.user.email)}"/>
25 </div>
25 </div>
26 <div>${entry.user.name} ${entry.user.lastname}</div>
26 <div>${entry.user.name} ${entry.user.lastname}</div>
27 <div style="padding-left: 45px;padding-top:5px">${h.action_parser(entry)} <br/>
27 <div style="padding-left: 45px;padding-top:5px;min-height:20px">${h.action_parser(entry)}</div>
28 <b>
28 <div style="float: left; padding-top: 8px;padding-left:18px">
29 ${h.action_parser_icon(entry)}
30 </div>
31 <div style="margin-left: 45px;padding-top: 10px">
32 <span style="font-weight: bold;font-size: 1.1em">
29 %if entry.repository:
33 %if entry.repository:
30 ${h.link_to(entry.repository.repo_name,
34 ${h.link_to(entry.repository.repo_name,
31 h.url('summary_home',repo_name=entry.repository.repo_name))}
35 h.url('summary_home',repo_name=entry.repository.repo_name))}
32 %else:
36 %else:
33 ${entry.repository_name}
37 ${entry.repository_name}
34 %endif
38 %endif
35 </b> - <span title="${entry.action_date}">${h.age(entry.action_date)}</span>
39 </span> - <span title="${entry.action_date}">${h.age(entry.action_date)}</span>
36 </div>
40 </div>
37 </div>
41 </div>
38 <div style="clear:both;border-bottom:1px dashed #DDD;padding:3px 3px;margin:0px 10px 0px 10px"></div>
42 <div style="clear:both;border-bottom:1px dashed #DDD;padding:3px 3px;margin:0px 10px 0px 10px"></div>
39 %endfor
43 %endfor
40 %else:
44 %else:
41 ${_('No entries yet')}
45 ${_('No entries yet')}
42 %endif
46 %endif
43 </div>
47 </div>
44 </div>
48 </div>
45
49
46 <div class="box box-right">
50 <div class="box box-right">
47 <!-- box / title -->
51 <!-- box / title -->
48 <div class="title">
52 <div class="title">
49 <h5>${_('Following')}</h5>
53 <h5>${_('Following')}</h5>
50 </div>
54 </div>
51 <div>
55 <div>
52 %if c.following:
56 %if c.following:
53 %for entry in c.following:
57 %for entry in c.following:
54 <div class="currently_following">
58 <div class="currently_following">
55 %if entry.follows_user_id:
59 %if entry.follows_user_id:
56 <img title="${_('following user')}" alt="${_('user')}" src="/images/icons/user.png"/>
60 <img title="${_('following user')}" alt="${_('user')}" src="/images/icons/user.png"/>
57 ${entry.follows_user.full_contact}
61 ${entry.follows_user.full_contact}
58 %endif
62 %endif
59
63
60 %if entry.follows_repo_id:
64 %if entry.follows_repo_id:
61
65
62 %if entry.follows_repository.private:
66 %if entry.follows_repository.private:
63 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="/images/icons/lock.png"/>
67 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="/images/icons/lock.png"/>
64 %else:
68 %else:
65 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="/images/icons/lock_open.png"/>
69 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="/images/icons/lock_open.png"/>
66 %endif
70 %endif
67
71
68 ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',
72 ${h.link_to(entry.follows_repository.repo_name,h.url('summary_home',
69 repo_name=entry.follows_repository.repo_name))}
73 repo_name=entry.follows_repository.repo_name))}
70
74
71 %endif
75 %endif
72 </div>
76 </div>
73 %endfor
77 %endfor
74 %else:
78 %else:
75 ${_('You are not following any users or repositories')}
79 ${_('You are not following any users or repositories')}
76 %endif
80 %endif
77 </div>
81 </div>
78 </div>
82 </div>
83
84 <script type="text/javascript">
85 YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
86 var el = e.target;
87 YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
88 YUD.setStyle(el.parentNode,'display','none');
89 });
90 </script>
91
79 </%def>
92 </%def>
General Comments 0
You need to be logged in to leave comments. Login now