##// END OF EJS Templates
ux: #4037 use gravatar for commit email and add tooltips for name + email
lisaq -
r410:e0937131 default
parent child Browse files
Show More
@@ -1,1886 +1,1900 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2016 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 Helper functions
23 23
24 24 Consists of functions to typically be used within templates, but also
25 25 available to Controllers. This module is available to both as 'h'.
26 26 """
27 27
28 28 import random
29 29 import hashlib
30 30 import StringIO
31 31 import urllib
32 32 import math
33 33 import logging
34 34 import re
35 35 import urlparse
36 36 import time
37 37 import string
38 38 import hashlib
39 39 import pygments
40 40
41 41 from datetime import datetime
42 42 from functools import partial
43 43 from pygments.formatters.html import HtmlFormatter
44 44 from pygments import highlight as code_highlight
45 45 from pygments.lexers import (
46 46 get_lexer_by_name, get_lexer_for_filename, get_lexer_for_mimetype)
47 47 from pylons import url
48 48 from pylons.i18n.translation import _, ungettext
49 49 from pyramid.threadlocal import get_current_request
50 50
51 51 from webhelpers.html import literal, HTML, escape
52 52 from webhelpers.html.tools import *
53 53 from webhelpers.html.builder import make_tag
54 54 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
55 55 end_form, file, form as wh_form, hidden, image, javascript_link, link_to, \
56 56 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
57 57 submit, text, password, textarea, title, ul, xml_declaration, radio
58 58 from webhelpers.html.tools import auto_link, button_to, highlight, \
59 59 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
60 60 from webhelpers.pylonslib import Flash as _Flash
61 61 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
62 62 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
63 63 replace_whitespace, urlify, truncate, wrap_paragraphs
64 64 from webhelpers.date import time_ago_in_words
65 65 from webhelpers.paginate import Page as _Page
66 66 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
67 67 convert_boolean_attrs, NotGiven, _make_safe_id_component
68 68 from webhelpers2.number import format_byte_size
69 69
70 70 from rhodecode.lib.annotate import annotate_highlight
71 71 from rhodecode.lib.action_parser import action_parser
72 72 from rhodecode.lib.ext_json import json
73 73 from rhodecode.lib.utils import repo_name_slug, get_custom_lexer
74 74 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
75 75 get_commit_safe, datetime_to_time, time_to_datetime, time_to_utcdatetime, \
76 76 AttributeDict, safe_int, md5, md5_safe
77 77 from rhodecode.lib.markup_renderer import MarkupRenderer
78 78 from rhodecode.lib.vcs.exceptions import CommitDoesNotExistError
79 79 from rhodecode.lib.vcs.backends.base import BaseChangeset, EmptyCommit
80 80 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
81 81 from rhodecode.model.changeset_status import ChangesetStatusModel
82 82 from rhodecode.model.db import Permission, User, Repository
83 83 from rhodecode.model.repo_group import RepoGroupModel
84 84 from rhodecode.model.settings import IssueTrackerSettingsModel
85 85
86 86 log = logging.getLogger(__name__)
87 87
88 88 DEFAULT_USER = User.DEFAULT_USER
89 89 DEFAULT_USER_EMAIL = User.DEFAULT_USER_EMAIL
90 90
91 91
92 92 def html_escape(text, html_escape_table=None):
93 93 """Produce entities within text."""
94 94 if not html_escape_table:
95 95 html_escape_table = {
96 96 "&": "&amp;",
97 97 '"': "&quot;",
98 98 "'": "&apos;",
99 99 ">": "&gt;",
100 100 "<": "&lt;",
101 101 }
102 102 return "".join(html_escape_table.get(c, c) for c in text)
103 103
104 104
105 105 def chop_at_smart(s, sub, inclusive=False, suffix_if_chopped=None):
106 106 """
107 107 Truncate string ``s`` at the first occurrence of ``sub``.
108 108
109 109 If ``inclusive`` is true, truncate just after ``sub`` rather than at it.
110 110 """
111 111 suffix_if_chopped = suffix_if_chopped or ''
112 112 pos = s.find(sub)
113 113 if pos == -1:
114 114 return s
115 115
116 116 if inclusive:
117 117 pos += len(sub)
118 118
119 119 chopped = s[:pos]
120 120 left = s[pos:].strip()
121 121
122 122 if left and suffix_if_chopped:
123 123 chopped += suffix_if_chopped
124 124
125 125 return chopped
126 126
127 127
128 128 def shorter(text, size=20):
129 129 postfix = '...'
130 130 if len(text) > size:
131 131 return text[:size - len(postfix)] + postfix
132 132 return text
133 133
134 134
135 135 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
136 136 """
137 137 Reset button
138 138 """
139 139 _set_input_attrs(attrs, type, name, value)
140 140 _set_id_attr(attrs, id, name)
141 141 convert_boolean_attrs(attrs, ["disabled"])
142 142 return HTML.input(**attrs)
143 143
144 144 reset = _reset
145 145 safeid = _make_safe_id_component
146 146
147 147
148 148 def branding(name, length=40):
149 149 return truncate(name, length, indicator="")
150 150
151 151
152 152 def FID(raw_id, path):
153 153 """
154 154 Creates a unique ID for filenode based on it's hash of path and commit
155 155 it's safe to use in urls
156 156
157 157 :param raw_id:
158 158 :param path:
159 159 """
160 160
161 161 return 'c-%s-%s' % (short_id(raw_id), md5_safe(path)[:12])
162 162
163 163
164 164 class _GetError(object):
165 165 """Get error from form_errors, and represent it as span wrapped error
166 166 message
167 167
168 168 :param field_name: field to fetch errors for
169 169 :param form_errors: form errors dict
170 170 """
171 171
172 172 def __call__(self, field_name, form_errors):
173 173 tmpl = """<span class="error_msg">%s</span>"""
174 174 if form_errors and field_name in form_errors:
175 175 return literal(tmpl % form_errors.get(field_name))
176 176
177 177 get_error = _GetError()
178 178
179 179
180 180 class _ToolTip(object):
181 181
182 182 def __call__(self, tooltip_title, trim_at=50):
183 183 """
184 184 Special function just to wrap our text into nice formatted
185 185 autowrapped text
186 186
187 187 :param tooltip_title:
188 188 """
189 189 tooltip_title = escape(tooltip_title)
190 190 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
191 191 return tooltip_title
192 192 tooltip = _ToolTip()
193 193
194 194
195 195 def files_breadcrumbs(repo_name, commit_id, file_path):
196 196 if isinstance(file_path, str):
197 197 file_path = safe_unicode(file_path)
198 198
199 199 # TODO: johbo: Is this always a url like path, or is this operating
200 200 # system dependent?
201 201 path_segments = file_path.split('/')
202 202
203 203 repo_name_html = escape(repo_name)
204 204 if len(path_segments) == 1 and path_segments[0] == '':
205 205 url_segments = [repo_name_html]
206 206 else:
207 207 url_segments = [
208 208 link_to(
209 209 repo_name_html,
210 210 url('files_home',
211 211 repo_name=repo_name,
212 212 revision=commit_id,
213 213 f_path=''),
214 214 class_='pjax-link')]
215 215
216 216 last_cnt = len(path_segments) - 1
217 217 for cnt, segment in enumerate(path_segments):
218 218 if not segment:
219 219 continue
220 220 segment_html = escape(segment)
221 221
222 222 if cnt != last_cnt:
223 223 url_segments.append(
224 224 link_to(
225 225 segment_html,
226 226 url('files_home',
227 227 repo_name=repo_name,
228 228 revision=commit_id,
229 229 f_path='/'.join(path_segments[:cnt + 1])),
230 230 class_='pjax-link'))
231 231 else:
232 232 url_segments.append(segment_html)
233 233
234 234 return literal('/'.join(url_segments))
235 235
236 236
237 237 class CodeHtmlFormatter(HtmlFormatter):
238 238 """
239 239 My code Html Formatter for source codes
240 240 """
241 241
242 242 def wrap(self, source, outfile):
243 243 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
244 244
245 245 def _wrap_code(self, source):
246 246 for cnt, it in enumerate(source):
247 247 i, t = it
248 248 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
249 249 yield i, t
250 250
251 251 def _wrap_tablelinenos(self, inner):
252 252 dummyoutfile = StringIO.StringIO()
253 253 lncount = 0
254 254 for t, line in inner:
255 255 if t:
256 256 lncount += 1
257 257 dummyoutfile.write(line)
258 258
259 259 fl = self.linenostart
260 260 mw = len(str(lncount + fl - 1))
261 261 sp = self.linenospecial
262 262 st = self.linenostep
263 263 la = self.lineanchors
264 264 aln = self.anchorlinenos
265 265 nocls = self.noclasses
266 266 if sp:
267 267 lines = []
268 268
269 269 for i in range(fl, fl + lncount):
270 270 if i % st == 0:
271 271 if i % sp == 0:
272 272 if aln:
273 273 lines.append('<a href="#%s%d" class="special">%*d</a>' %
274 274 (la, i, mw, i))
275 275 else:
276 276 lines.append('<span class="special">%*d</span>' % (mw, i))
277 277 else:
278 278 if aln:
279 279 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
280 280 else:
281 281 lines.append('%*d' % (mw, i))
282 282 else:
283 283 lines.append('')
284 284 ls = '\n'.join(lines)
285 285 else:
286 286 lines = []
287 287 for i in range(fl, fl + lncount):
288 288 if i % st == 0:
289 289 if aln:
290 290 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
291 291 else:
292 292 lines.append('%*d' % (mw, i))
293 293 else:
294 294 lines.append('')
295 295 ls = '\n'.join(lines)
296 296
297 297 # in case you wonder about the seemingly redundant <div> here: since the
298 298 # content in the other cell also is wrapped in a div, some browsers in
299 299 # some configurations seem to mess up the formatting...
300 300 if nocls:
301 301 yield 0, ('<table class="%stable">' % self.cssclass +
302 302 '<tr><td><div class="linenodiv" '
303 303 'style="background-color: #f0f0f0; padding-right: 10px">'
304 304 '<pre style="line-height: 125%">' +
305 305 ls + '</pre></div></td><td id="hlcode" class="code">')
306 306 else:
307 307 yield 0, ('<table class="%stable">' % self.cssclass +
308 308 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
309 309 ls + '</pre></div></td><td id="hlcode" class="code">')
310 310 yield 0, dummyoutfile.getvalue()
311 311 yield 0, '</td></tr></table>'
312 312
313 313
314 314 class SearchContentCodeHtmlFormatter(CodeHtmlFormatter):
315 315 def __init__(self, **kw):
316 316 # only show these line numbers if set
317 317 self.only_lines = kw.pop('only_line_numbers', [])
318 318 self.query_terms = kw.pop('query_terms', [])
319 319 self.max_lines = kw.pop('max_lines', 5)
320 320 self.line_context = kw.pop('line_context', 3)
321 321 self.url = kw.pop('url', None)
322 322
323 323 super(CodeHtmlFormatter, self).__init__(**kw)
324 324
325 325 def _wrap_code(self, source):
326 326 for cnt, it in enumerate(source):
327 327 i, t = it
328 328 t = '<pre>%s</pre>' % t
329 329 yield i, t
330 330
331 331 def _wrap_tablelinenos(self, inner):
332 332 yield 0, '<table class="code-highlight %stable">' % self.cssclass
333 333
334 334 last_shown_line_number = 0
335 335 current_line_number = 1
336 336
337 337 for t, line in inner:
338 338 if not t:
339 339 yield t, line
340 340 continue
341 341
342 342 if current_line_number in self.only_lines:
343 343 if last_shown_line_number + 1 != current_line_number:
344 344 yield 0, '<tr>'
345 345 yield 0, '<td class="line">...</td>'
346 346 yield 0, '<td id="hlcode" class="code"></td>'
347 347 yield 0, '</tr>'
348 348
349 349 yield 0, '<tr>'
350 350 if self.url:
351 351 yield 0, '<td class="line"><a href="%s#L%i">%i</a></td>' % (
352 352 self.url, current_line_number, current_line_number)
353 353 else:
354 354 yield 0, '<td class="line"><a href="">%i</a></td>' % (
355 355 current_line_number)
356 356 yield 0, '<td id="hlcode" class="code">' + line + '</td>'
357 357 yield 0, '</tr>'
358 358
359 359 last_shown_line_number = current_line_number
360 360
361 361 current_line_number += 1
362 362
363 363
364 364 yield 0, '</table>'
365 365
366 366
367 367 def extract_phrases(text_query):
368 368 """
369 369 Extracts phrases from search term string making sure phrases
370 370 contained in double quotes are kept together - and discarding empty values
371 371 or fully whitespace values eg.
372 372
373 373 'some text "a phrase" more' => ['some', 'text', 'a phrase', 'more']
374 374
375 375 """
376 376
377 377 in_phrase = False
378 378 buf = ''
379 379 phrases = []
380 380 for char in text_query:
381 381 if in_phrase:
382 382 if char == '"': # end phrase
383 383 phrases.append(buf)
384 384 buf = ''
385 385 in_phrase = False
386 386 continue
387 387 else:
388 388 buf += char
389 389 continue
390 390 else:
391 391 if char == '"': # start phrase
392 392 in_phrase = True
393 393 phrases.append(buf)
394 394 buf = ''
395 395 continue
396 396 elif char == ' ':
397 397 phrases.append(buf)
398 398 buf = ''
399 399 continue
400 400 else:
401 401 buf += char
402 402
403 403 phrases.append(buf)
404 404 phrases = [phrase.strip() for phrase in phrases if phrase.strip()]
405 405 return phrases
406 406
407 407
408 408 def get_matching_offsets(text, phrases):
409 409 """
410 410 Returns a list of string offsets in `text` that the list of `terms` match
411 411
412 412 >>> get_matching_offsets('some text here', ['some', 'here'])
413 413 [(0, 4), (10, 14)]
414 414
415 415 """
416 416 offsets = []
417 417 for phrase in phrases:
418 418 for match in re.finditer(phrase, text):
419 419 offsets.append((match.start(), match.end()))
420 420
421 421 return offsets
422 422
423 423
424 424 def normalize_text_for_matching(x):
425 425 """
426 426 Replaces all non alnum characters to spaces and lower cases the string,
427 427 useful for comparing two text strings without punctuation
428 428 """
429 429 return re.sub(r'[^\w]', ' ', x.lower())
430 430
431 431
432 432 def get_matching_line_offsets(lines, terms):
433 433 """ Return a set of `lines` indices (starting from 1) matching a
434 434 text search query, along with `context` lines above/below matching lines
435 435
436 436 :param lines: list of strings representing lines
437 437 :param terms: search term string to match in lines eg. 'some text'
438 438 :param context: number of lines above/below a matching line to add to result
439 439 :param max_lines: cut off for lines of interest
440 440 eg.
441 441
442 442 text = '''
443 443 words words words
444 444 words words words
445 445 some text some
446 446 words words words
447 447 words words words
448 448 text here what
449 449 '''
450 450 get_matching_line_offsets(text, 'text', context=1)
451 451 {3: [(5, 9)], 6: [(0, 4)]]
452 452
453 453 """
454 454 matching_lines = {}
455 455 phrases = [normalize_text_for_matching(phrase)
456 456 for phrase in extract_phrases(terms)]
457 457
458 458 for line_index, line in enumerate(lines, start=1):
459 459 match_offsets = get_matching_offsets(
460 460 normalize_text_for_matching(line), phrases)
461 461 if match_offsets:
462 462 matching_lines[line_index] = match_offsets
463 463
464 464 return matching_lines
465 465
466 466
467 467 def get_lexer_safe(mimetype=None, filepath=None):
468 468 """
469 469 Tries to return a relevant pygments lexer using mimetype/filepath name,
470 470 defaulting to plain text if none could be found
471 471 """
472 472 lexer = None
473 473 try:
474 474 if mimetype:
475 475 lexer = get_lexer_for_mimetype(mimetype)
476 476 if not lexer:
477 477 lexer = get_lexer_for_filename(filepath)
478 478 except pygments.util.ClassNotFound:
479 479 pass
480 480
481 481 if not lexer:
482 482 lexer = get_lexer_by_name('text')
483 483
484 484 return lexer
485 485
486 486
487 487 def pygmentize(filenode, **kwargs):
488 488 """
489 489 pygmentize function using pygments
490 490
491 491 :param filenode:
492 492 """
493 493 lexer = get_custom_lexer(filenode.extension) or filenode.lexer
494 494 return literal(code_highlight(filenode.content, lexer,
495 495 CodeHtmlFormatter(**kwargs)))
496 496
497 497
498 498 def pygmentize_annotation(repo_name, filenode, **kwargs):
499 499 """
500 500 pygmentize function for annotation
501 501
502 502 :param filenode:
503 503 """
504 504
505 505 color_dict = {}
506 506
507 507 def gen_color(n=10000):
508 508 """generator for getting n of evenly distributed colors using
509 509 hsv color and golden ratio. It always return same order of colors
510 510
511 511 :returns: RGB tuple
512 512 """
513 513
514 514 def hsv_to_rgb(h, s, v):
515 515 if s == 0.0:
516 516 return v, v, v
517 517 i = int(h * 6.0) # XXX assume int() truncates!
518 518 f = (h * 6.0) - i
519 519 p = v * (1.0 - s)
520 520 q = v * (1.0 - s * f)
521 521 t = v * (1.0 - s * (1.0 - f))
522 522 i = i % 6
523 523 if i == 0:
524 524 return v, t, p
525 525 if i == 1:
526 526 return q, v, p
527 527 if i == 2:
528 528 return p, v, t
529 529 if i == 3:
530 530 return p, q, v
531 531 if i == 4:
532 532 return t, p, v
533 533 if i == 5:
534 534 return v, p, q
535 535
536 536 golden_ratio = 0.618033988749895
537 537 h = 0.22717784590367374
538 538
539 539 for _ in xrange(n):
540 540 h += golden_ratio
541 541 h %= 1
542 542 HSV_tuple = [h, 0.95, 0.95]
543 543 RGB_tuple = hsv_to_rgb(*HSV_tuple)
544 544 yield map(lambda x: str(int(x * 256)), RGB_tuple)
545 545
546 546 cgenerator = gen_color()
547 547
548 548 def get_color_string(commit_id):
549 549 if commit_id in color_dict:
550 550 col = color_dict[commit_id]
551 551 else:
552 552 col = color_dict[commit_id] = cgenerator.next()
553 553 return "color: rgb(%s)! important;" % (', '.join(col))
554 554
555 555 def url_func(repo_name):
556 556
557 557 def _url_func(commit):
558 558 author = commit.author
559 559 date = commit.date
560 560 message = tooltip(commit.message)
561 561
562 562 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
563 563 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
564 564 "</b> %s<br/></div>")
565 565
566 566 tooltip_html = tooltip_html % (author, date, message)
567 567 lnk_format = '%5s:%s' % ('r%s' % commit.idx, commit.short_id)
568 568 uri = link_to(
569 569 lnk_format,
570 570 url('changeset_home', repo_name=repo_name,
571 571 revision=commit.raw_id),
572 572 style=get_color_string(commit.raw_id),
573 573 class_='tooltip',
574 574 title=tooltip_html
575 575 )
576 576
577 577 uri += '\n'
578 578 return uri
579 579 return _url_func
580 580
581 581 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
582 582
583 583
584 584 def is_following_repo(repo_name, user_id):
585 585 from rhodecode.model.scm import ScmModel
586 586 return ScmModel().is_following_repo(repo_name, user_id)
587 587
588 588
589 589 class _Message(object):
590 590 """A message returned by ``Flash.pop_messages()``.
591 591
592 592 Converting the message to a string returns the message text. Instances
593 593 also have the following attributes:
594 594
595 595 * ``message``: the message text.
596 596 * ``category``: the category specified when the message was created.
597 597 """
598 598
599 599 def __init__(self, category, message):
600 600 self.category = category
601 601 self.message = message
602 602
603 603 def __str__(self):
604 604 return self.message
605 605
606 606 __unicode__ = __str__
607 607
608 608 def __html__(self):
609 609 return escape(safe_unicode(self.message))
610 610
611 611
612 612 class Flash(_Flash):
613 613
614 614 def pop_messages(self):
615 615 """Return all accumulated messages and delete them from the session.
616 616
617 617 The return value is a list of ``Message`` objects.
618 618 """
619 619 from pylons import session
620 620
621 621 messages = []
622 622
623 623 # Pop the 'old' pylons flash messages. They are tuples of the form
624 624 # (category, message)
625 625 for cat, msg in session.pop(self.session_key, []):
626 626 messages.append(_Message(cat, msg))
627 627
628 628 # Pop the 'new' pyramid flash messages for each category as list
629 629 # of strings.
630 630 for cat in self.categories:
631 631 for msg in session.pop_flash(queue=cat):
632 632 messages.append(_Message(cat, msg))
633 633 # Map messages from the default queue to the 'notice' category.
634 634 for msg in session.pop_flash():
635 635 messages.append(_Message('notice', msg))
636 636
637 637 session.save()
638 638 return messages
639 639
640 640 flash = Flash()
641 641
642 642 #==============================================================================
643 643 # SCM FILTERS available via h.
644 644 #==============================================================================
645 645 from rhodecode.lib.vcs.utils import author_name, author_email
646 646 from rhodecode.lib.utils2 import credentials_filter, age as _age
647 647 from rhodecode.model.db import User, ChangesetStatus
648 648
649 649 age = _age
650 650 capitalize = lambda x: x.capitalize()
651 651 email = author_email
652 652 short_id = lambda x: x[:12]
653 653 hide_credentials = lambda x: ''.join(credentials_filter(x))
654 654
655 655
656 656 def age_component(datetime_iso, value=None, time_is_local=False):
657 657 title = value or format_date(datetime_iso)
658 658
659 659 # detect if we have a timezone info, otherwise, add it
660 660 if isinstance(datetime_iso, datetime) and not datetime_iso.tzinfo:
661 661 tzinfo = '+00:00'
662 662
663 663 if time_is_local:
664 664 tzinfo = time.strftime("+%H:%M",
665 665 time.gmtime(
666 666 (datetime.now() - datetime.utcnow()).seconds + 1
667 667 )
668 668 )
669 669
670 670 return literal(
671 671 '<time class="timeago tooltip" '
672 672 'title="{1}" datetime="{0}{2}">{1}</time>'.format(
673 673 datetime_iso, title, tzinfo))
674 674
675 675
676 676 def _shorten_commit_id(commit_id):
677 677 from rhodecode import CONFIG
678 678 def_len = safe_int(CONFIG.get('rhodecode_show_sha_length', 12))
679 679 return commit_id[:def_len]
680 680
681 681
682 682 def show_id(commit):
683 683 """
684 684 Configurable function that shows ID
685 685 by default it's r123:fffeeefffeee
686 686
687 687 :param commit: commit instance
688 688 """
689 689 from rhodecode import CONFIG
690 690 show_idx = str2bool(CONFIG.get('rhodecode_show_revision_number', True))
691 691
692 692 raw_id = _shorten_commit_id(commit.raw_id)
693 693 if show_idx:
694 694 return 'r%s:%s' % (commit.idx, raw_id)
695 695 else:
696 696 return '%s' % (raw_id, )
697 697
698 698
699 699 def format_date(date):
700 700 """
701 701 use a standardized formatting for dates used in RhodeCode
702 702
703 703 :param date: date/datetime object
704 704 :return: formatted date
705 705 """
706 706
707 707 if date:
708 708 _fmt = "%a, %d %b %Y %H:%M:%S"
709 709 return safe_unicode(date.strftime(_fmt))
710 710
711 711 return u""
712 712
713 713
714 714 class _RepoChecker(object):
715 715
716 716 def __init__(self, backend_alias):
717 717 self._backend_alias = backend_alias
718 718
719 719 def __call__(self, repository):
720 720 if hasattr(repository, 'alias'):
721 721 _type = repository.alias
722 722 elif hasattr(repository, 'repo_type'):
723 723 _type = repository.repo_type
724 724 else:
725 725 _type = repository
726 726 return _type == self._backend_alias
727 727
728 728 is_git = _RepoChecker('git')
729 729 is_hg = _RepoChecker('hg')
730 730 is_svn = _RepoChecker('svn')
731 731
732 732
733 733 def get_repo_type_by_name(repo_name):
734 734 repo = Repository.get_by_repo_name(repo_name)
735 735 return repo.repo_type
736 736
737 737
738 738 def is_svn_without_proxy(repository):
739 739 from rhodecode import CONFIG
740 740 if is_svn(repository):
741 741 if not CONFIG.get('rhodecode_proxy_subversion_http_requests', False):
742 742 return True
743 743 return False
744 744
745 745
746 746 def discover_user(author):
747 747 """
748 748 Tries to discover RhodeCode User based on the autho string. Author string
749 749 is typically `FirstName LastName <email@address.com>`
750 750 """
751 751
752 752 # if author is already an instance use it for extraction
753 753 if isinstance(author, User):
754 754 return author
755 755
756 756 # Valid email in the attribute passed, see if they're in the system
757 757 _email = author_email(author)
758 758 if _email != '':
759 759 user = User.get_by_email(_email, case_insensitive=True, cache=True)
760 760 if user is not None:
761 761 return user
762 762
763 763 # Maybe it's a username, we try to extract it and fetch by username ?
764 764 _author = author_name(author)
765 765 user = User.get_by_username(_author, case_insensitive=True, cache=True)
766 766 if user is not None:
767 767 return user
768 768
769 769 return None
770 770
771 771
772 772 def email_or_none(author):
773 773 # extract email from the commit string
774 774 _email = author_email(author)
775 775
776 776 # If we have an email, use it, otherwise
777 777 # see if it contains a username we can get an email from
778 778 if _email != '':
779 779 return _email
780 780 else:
781 781 user = User.get_by_username(author_name(author), case_insensitive=True,
782 782 cache=True)
783 783
784 784 if user is not None:
785 785 return user.email
786 786
787 787 # No valid email, not a valid user in the system, none!
788 788 return None
789 789
790 790
791 791 def link_to_user(author, length=0, **kwargs):
792 792 user = discover_user(author)
793 793 # user can be None, but if we have it already it means we can re-use it
794 794 # in the person() function, so we save 1 intensive-query
795 795 if user:
796 796 author = user
797 797
798 798 display_person = person(author, 'username_or_name_or_email')
799 799 if length:
800 800 display_person = shorter(display_person, length)
801 801
802 802 if user:
803 803 return link_to(
804 804 escape(display_person),
805 805 url('user_profile', username=user.username),
806 806 **kwargs)
807 807 else:
808 808 return escape(display_person)
809 809
810 810
811 811 def person(author, show_attr="username_and_name"):
812 812 user = discover_user(author)
813 813 if user:
814 814 return getattr(user, show_attr)
815 815 else:
816 816 _author = author_name(author)
817 817 _email = email(author)
818 818 return _author or _email
819 819
820 820
821 def author_string(email):
822 if email:
823 user = User.get_by_email(email, case_insensitive=True, cache=True)
824 if user:
825 if user.firstname or user.lastname:
826 return '%s %s &lt;%s&gt;' % (user.firstname, user.lastname, email)
827 else:
828 return email
829 else:
830 return email
831 else:
832 return None
833
834
821 835 def person_by_id(id_, show_attr="username_and_name"):
822 836 # attr to return from fetched user
823 837 person_getter = lambda usr: getattr(usr, show_attr)
824 838
825 839 #maybe it's an ID ?
826 840 if str(id_).isdigit() or isinstance(id_, int):
827 841 id_ = int(id_)
828 842 user = User.get(id_)
829 843 if user is not None:
830 844 return person_getter(user)
831 845 return id_
832 846
833 847
834 848 def gravatar_with_user(author, show_disabled=False):
835 849 from rhodecode.lib.utils import PartialRenderer
836 850 _render = PartialRenderer('base/base.html')
837 851 return _render('gravatar_with_user', author, show_disabled=show_disabled)
838 852
839 853
840 854 def desc_stylize(value):
841 855 """
842 856 converts tags from value into html equivalent
843 857
844 858 :param value:
845 859 """
846 860 if not value:
847 861 return ''
848 862
849 863 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
850 864 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
851 865 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
852 866 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
853 867 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z0-9\-\/]*)\]',
854 868 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
855 869 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/\#\+]*)\]',
856 870 '<div class="metatag" tag="lang">\\2</div>', value)
857 871 value = re.sub(r'\[([a-z]+)\]',
858 872 '<div class="metatag" tag="\\1">\\1</div>', value)
859 873
860 874 return value
861 875
862 876
863 877 def escaped_stylize(value):
864 878 """
865 879 converts tags from value into html equivalent, but escaping its value first
866 880 """
867 881 if not value:
868 882 return ''
869 883
870 884 # Using default webhelper escape method, but has to force it as a
871 885 # plain unicode instead of a markup tag to be used in regex expressions
872 886 value = unicode(escape(safe_unicode(value)))
873 887
874 888 value = re.sub(r'\[see\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
875 889 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
876 890 value = re.sub(r'\[license\ \=\&gt;\ *([a-zA-Z0-9\/\=\?\&amp;\ \:\/\.\-]*)\]',
877 891 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
878 892 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\&gt;\ *([a-zA-Z0-9\-\/]*)\]',
879 893 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
880 894 value = re.sub(r'\[(lang|language)\ \=\&gt;\ *([a-zA-Z\-\/\#\+]*)\]',
881 895 '<div class="metatag" tag="lang">\\2</div>', value)
882 896 value = re.sub(r'\[([a-z]+)\]',
883 897 '<div class="metatag" tag="\\1">\\1</div>', value)
884 898
885 899 return value
886 900
887 901
888 902 def bool2icon(value):
889 903 """
890 904 Returns boolean value of a given value, represented as html element with
891 905 classes that will represent icons
892 906
893 907 :param value: given value to convert to html node
894 908 """
895 909
896 910 if value: # does bool conversion
897 911 return HTML.tag('i', class_="icon-true")
898 912 else: # not true as bool
899 913 return HTML.tag('i', class_="icon-false")
900 914
901 915
902 916 #==============================================================================
903 917 # PERMS
904 918 #==============================================================================
905 919 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
906 920 HasRepoPermissionAny, HasRepoPermissionAll, HasRepoGroupPermissionAll, \
907 921 HasRepoGroupPermissionAny, HasRepoPermissionAnyApi, get_csrf_token
908 922
909 923
910 924 #==============================================================================
911 925 # GRAVATAR URL
912 926 #==============================================================================
913 927 class InitialsGravatar(object):
914 928 def __init__(self, email_address, first_name, last_name, size=30,
915 929 background=None, text_color='#fff'):
916 930 self.size = size
917 931 self.first_name = first_name
918 932 self.last_name = last_name
919 933 self.email_address = email_address
920 934 self.background = background or self.str2color(email_address)
921 935 self.text_color = text_color
922 936
923 937 def get_color_bank(self):
924 938 """
925 939 returns a predefined list of colors that gravatars can use.
926 940 Those are randomized distinct colors that guarantee readability and
927 941 uniqueness.
928 942
929 943 generated with: http://phrogz.net/css/distinct-colors.html
930 944 """
931 945 return [
932 946 '#bf3030', '#a67f53', '#00ff00', '#5989b3', '#392040', '#d90000',
933 947 '#402910', '#204020', '#79baf2', '#a700b3', '#bf6060', '#7f5320',
934 948 '#008000', '#003059', '#ee00ff', '#ff0000', '#8c4b00', '#007300',
935 949 '#005fb3', '#de73e6', '#ff4040', '#ffaa00', '#3df255', '#203140',
936 950 '#47004d', '#591616', '#664400', '#59b365', '#0d2133', '#83008c',
937 951 '#592d2d', '#bf9f60', '#73e682', '#1d3f73', '#73006b', '#402020',
938 952 '#b2862d', '#397341', '#597db3', '#e600d6', '#a60000', '#736039',
939 953 '#00b318', '#79aaf2', '#330d30', '#ff8080', '#403010', '#16591f',
940 954 '#002459', '#8c4688', '#e50000', '#ffbf40', '#00732e', '#102340',
941 955 '#bf60ac', '#8c4646', '#cc8800', '#00a642', '#1d3473', '#b32d98',
942 956 '#660e00', '#ffd580', '#80ffb2', '#7391e6', '#733967', '#d97b6c',
943 957 '#8c5e00', '#59b389', '#3967e6', '#590047', '#73281d', '#665200',
944 958 '#00e67a', '#2d50b3', '#8c2377', '#734139', '#b2982d', '#16593a',
945 959 '#001859', '#ff00aa', '#a65e53', '#ffcc00', '#0d3321', '#2d3959',
946 960 '#731d56', '#401610', '#4c3d00', '#468c6c', '#002ca6', '#d936a3',
947 961 '#d94c36', '#403920', '#36d9a3', '#0d1733', '#592d4a', '#993626',
948 962 '#cca300', '#00734d', '#46598c', '#8c005e', '#7f1100', '#8c7000',
949 963 '#00a66f', '#7382e6', '#b32d74', '#d9896c', '#ffe680', '#1d7362',
950 964 '#364cd9', '#73003d', '#d93a00', '#998a4d', '#59b3a1', '#5965b3',
951 965 '#e5007a', '#73341d', '#665f00', '#00b38f', '#0018b3', '#59163a',
952 966 '#b2502d', '#bfb960', '#00ffcc', '#23318c', '#a6537f', '#734939',
953 967 '#b2a700', '#104036', '#3d3df2', '#402031', '#e56739', '#736f39',
954 968 '#79f2ea', '#000059', '#401029', '#4c1400', '#ffee00', '#005953',
955 969 '#101040', '#990052', '#402820', '#403d10', '#00ffee', '#0000d9',
956 970 '#ff80c4', '#a66953', '#eeff00', '#00ccbe', '#8080ff', '#e673a1',
957 971 '#a62c00', '#474d00', '#1a3331', '#46468c', '#733950', '#662900',
958 972 '#858c23', '#238c85', '#0f0073', '#b20047', '#d9986c', '#becc00',
959 973 '#396f73', '#281d73', '#ff0066', '#ff6600', '#dee673', '#59adb3',
960 974 '#6559b3', '#590024', '#b2622d', '#98b32d', '#36ced9', '#332d59',
961 975 '#40001a', '#733f1d', '#526600', '#005359', '#242040', '#bf6079',
962 976 '#735039', '#cef23d', '#007780', '#5630bf', '#66001b', '#b24700',
963 977 '#acbf60', '#1d6273', '#25008c', '#731d34', '#a67453', '#50592d',
964 978 '#00ccff', '#6600ff', '#ff0044', '#4c1f00', '#8a994d', '#79daf2',
965 979 '#a173e6', '#d93662', '#402310', '#aaff00', '#2d98b3', '#8c40ff',
966 980 '#592d39', '#ff8c40', '#354020', '#103640', '#1a0040', '#331a20',
967 981 '#331400', '#334d00', '#1d5673', '#583973', '#7f0022', '#4c3626',
968 982 '#88cc00', '#36a3d9', '#3d0073', '#d9364c', '#33241a', '#698c23',
969 983 '#5995b3', '#300059', '#e57382', '#7f3300', '#366600', '#00aaff',
970 984 '#3a1659', '#733941', '#663600', '#74b32d', '#003c59', '#7f53a6',
971 985 '#73000f', '#ff8800', '#baf279', '#79caf2', '#291040', '#a6293a',
972 986 '#b2742d', '#587339', '#0077b3', '#632699', '#400009', '#d9a66c',
973 987 '#294010', '#2d4a59', '#aa00ff', '#4c131b', '#b25f00', '#5ce600',
974 988 '#267399', '#a336d9', '#990014', '#664e33', '#86bf60', '#0088ff',
975 989 '#7700b3', '#593a16', '#073300', '#1d4b73', '#ac60bf', '#e59539',
976 990 '#4f8c46', '#368dd9', '#5c0073'
977 991 ]
978 992
979 993 def rgb_to_hex_color(self, rgb_tuple):
980 994 """
981 995 Converts an rgb_tuple passed to an hex color.
982 996
983 997 :param rgb_tuple: tuple with 3 ints represents rgb color space
984 998 """
985 999 return '#' + ("".join(map(chr, rgb_tuple)).encode('hex'))
986 1000
987 1001 def email_to_int_list(self, email_str):
988 1002 """
989 1003 Get every byte of the hex digest value of email and turn it to integer.
990 1004 It's going to be always between 0-255
991 1005 """
992 1006 digest = md5_safe(email_str.lower())
993 1007 return [int(digest[i * 2:i * 2 + 2], 16) for i in range(16)]
994 1008
995 1009 def pick_color_bank_index(self, email_str, color_bank):
996 1010 return self.email_to_int_list(email_str)[0] % len(color_bank)
997 1011
998 1012 def str2color(self, email_str):
999 1013 """
1000 1014 Tries to map in a stable algorithm an email to color
1001 1015
1002 1016 :param email_str:
1003 1017 """
1004 1018 color_bank = self.get_color_bank()
1005 1019 # pick position (module it's length so we always find it in the
1006 1020 # bank even if it's smaller than 256 values
1007 1021 pos = self.pick_color_bank_index(email_str, color_bank)
1008 1022 return color_bank[pos]
1009 1023
1010 1024 def normalize_email(self, email_address):
1011 1025 import unicodedata
1012 1026 # default host used to fill in the fake/missing email
1013 1027 default_host = u'localhost'
1014 1028
1015 1029 if not email_address:
1016 1030 email_address = u'%s@%s' % (User.DEFAULT_USER, default_host)
1017 1031
1018 1032 email_address = safe_unicode(email_address)
1019 1033
1020 1034 if u'@' not in email_address:
1021 1035 email_address = u'%s@%s' % (email_address, default_host)
1022 1036
1023 1037 if email_address.endswith(u'@'):
1024 1038 email_address = u'%s%s' % (email_address, default_host)
1025 1039
1026 1040 email_address = unicodedata.normalize('NFKD', email_address)\
1027 1041 .encode('ascii', 'ignore')
1028 1042 return email_address
1029 1043
1030 1044 def get_initials(self):
1031 1045 """
1032 1046 Returns 2 letter initials calculated based on the input.
1033 1047 The algorithm picks first given email address, and takes first letter
1034 1048 of part before @, and then the first letter of server name. In case
1035 1049 the part before @ is in a format of `somestring.somestring2` it replaces
1036 1050 the server letter with first letter of somestring2
1037 1051
1038 1052 In case function was initialized with both first and lastname, this
1039 1053 overrides the extraction from email by first letter of the first and
1040 1054 last name. We add special logic to that functionality, In case Full name
1041 1055 is compound, like Guido Von Rossum, we use last part of the last name
1042 1056 (Von Rossum) picking `R`.
1043 1057
1044 1058 Function also normalizes the non-ascii characters to they ascii
1045 1059 representation, eg Ą => A
1046 1060 """
1047 1061 import unicodedata
1048 1062 # replace non-ascii to ascii
1049 1063 first_name = unicodedata.normalize(
1050 1064 'NFKD', safe_unicode(self.first_name)).encode('ascii', 'ignore')
1051 1065 last_name = unicodedata.normalize(
1052 1066 'NFKD', safe_unicode(self.last_name)).encode('ascii', 'ignore')
1053 1067
1054 1068 # do NFKD encoding, and also make sure email has proper format
1055 1069 email_address = self.normalize_email(self.email_address)
1056 1070
1057 1071 # first push the email initials
1058 1072 prefix, server = email_address.split('@', 1)
1059 1073
1060 1074 # check if prefix is maybe a 'firstname.lastname' syntax
1061 1075 _dot_split = prefix.rsplit('.', 1)
1062 1076 if len(_dot_split) == 2:
1063 1077 initials = [_dot_split[0][0], _dot_split[1][0]]
1064 1078 else:
1065 1079 initials = [prefix[0], server[0]]
1066 1080
1067 1081 # then try to replace either firtname or lastname
1068 1082 fn_letter = (first_name or " ")[0].strip()
1069 1083 ln_letter = (last_name.split(' ', 1)[-1] or " ")[0].strip()
1070 1084
1071 1085 if fn_letter:
1072 1086 initials[0] = fn_letter
1073 1087
1074 1088 if ln_letter:
1075 1089 initials[1] = ln_letter
1076 1090
1077 1091 return ''.join(initials).upper()
1078 1092
1079 1093 def get_img_data_by_type(self, font_family, img_type):
1080 1094 default_user = """
1081 1095 <svg xmlns="http://www.w3.org/2000/svg"
1082 1096 version="1.1" x="0px" y="0px" width="{size}" height="{size}"
1083 1097 viewBox="-15 -10 439.165 429.164"
1084 1098
1085 1099 xml:space="preserve"
1086 1100 style="background:{background};" >
1087 1101
1088 1102 <path d="M204.583,216.671c50.664,0,91.74-48.075,
1089 1103 91.74-107.378c0-82.237-41.074-107.377-91.74-107.377
1090 1104 c-50.668,0-91.74,25.14-91.74,107.377C112.844,
1091 1105 168.596,153.916,216.671,
1092 1106 204.583,216.671z" fill="{text_color}"/>
1093 1107 <path d="M407.164,374.717L360.88,
1094 1108 270.454c-2.117-4.771-5.836-8.728-10.465-11.138l-71.83-37.392
1095 1109 c-1.584-0.823-3.502-0.663-4.926,0.415c-20.316,
1096 1110 15.366-44.203,23.488-69.076,23.488c-24.877,
1097 1111 0-48.762-8.122-69.078-23.488
1098 1112 c-1.428-1.078-3.346-1.238-4.93-0.415L58.75,
1099 1113 259.316c-4.631,2.41-8.346,6.365-10.465,11.138L2.001,374.717
1100 1114 c-3.191,7.188-2.537,15.412,1.75,22.005c4.285,
1101 1115 6.592,11.537,10.526,19.4,10.526h362.861c7.863,0,15.117-3.936,
1102 1116 19.402-10.527 C409.699,390.129,
1103 1117 410.355,381.902,407.164,374.717z" fill="{text_color}"/>
1104 1118 </svg>""".format(
1105 1119 size=self.size,
1106 1120 background='#979797', # @grey4
1107 1121 text_color=self.text_color,
1108 1122 font_family=font_family)
1109 1123
1110 1124 return {
1111 1125 "default_user": default_user
1112 1126 }[img_type]
1113 1127
1114 1128 def get_img_data(self, svg_type=None):
1115 1129 """
1116 1130 generates the svg metadata for image
1117 1131 """
1118 1132
1119 1133 font_family = ','.join([
1120 1134 'proximanovaregular',
1121 1135 'Proxima Nova Regular',
1122 1136 'Proxima Nova',
1123 1137 'Arial',
1124 1138 'Lucida Grande',
1125 1139 'sans-serif'
1126 1140 ])
1127 1141 if svg_type:
1128 1142 return self.get_img_data_by_type(font_family, svg_type)
1129 1143
1130 1144 initials = self.get_initials()
1131 1145 img_data = """
1132 1146 <svg xmlns="http://www.w3.org/2000/svg" pointer-events="none"
1133 1147 width="{size}" height="{size}"
1134 1148 style="width: 100%; height: 100%; background-color: {background}"
1135 1149 viewBox="0 0 {size} {size}">
1136 1150 <text text-anchor="middle" y="50%" x="50%" dy="0.35em"
1137 1151 pointer-events="auto" fill="{text_color}"
1138 1152 font-family="{font_family}"
1139 1153 style="font-weight: 400; font-size: {f_size}px;">{text}
1140 1154 </text>
1141 1155 </svg>""".format(
1142 1156 size=self.size,
1143 1157 f_size=self.size/1.85, # scale the text inside the box nicely
1144 1158 background=self.background,
1145 1159 text_color=self.text_color,
1146 1160 text=initials.upper(),
1147 1161 font_family=font_family)
1148 1162
1149 1163 return img_data
1150 1164
1151 1165 def generate_svg(self, svg_type=None):
1152 1166 img_data = self.get_img_data(svg_type)
1153 1167 return "data:image/svg+xml;base64,%s" % img_data.encode('base64')
1154 1168
1155 1169
1156 1170 def initials_gravatar(email_address, first_name, last_name, size=30):
1157 1171 svg_type = None
1158 1172 if email_address == User.DEFAULT_USER_EMAIL:
1159 1173 svg_type = 'default_user'
1160 1174 klass = InitialsGravatar(email_address, first_name, last_name, size)
1161 1175 return klass.generate_svg(svg_type=svg_type)
1162 1176
1163 1177
1164 1178 def gravatar_url(email_address, size=30):
1165 1179 # doh, we need to re-import those to mock it later
1166 1180 from pylons import tmpl_context as c
1167 1181
1168 1182 _use_gravatar = c.visual.use_gravatar
1169 1183 _gravatar_url = c.visual.gravatar_url or User.DEFAULT_GRAVATAR_URL
1170 1184
1171 1185 email_address = email_address or User.DEFAULT_USER_EMAIL
1172 1186 if isinstance(email_address, unicode):
1173 1187 # hashlib crashes on unicode items
1174 1188 email_address = safe_str(email_address)
1175 1189
1176 1190 # empty email or default user
1177 1191 if not email_address or email_address == User.DEFAULT_USER_EMAIL:
1178 1192 return initials_gravatar(User.DEFAULT_USER_EMAIL, '', '', size=size)
1179 1193
1180 1194 if _use_gravatar:
1181 1195 # TODO: Disuse pyramid thread locals. Think about another solution to
1182 1196 # get the host and schema here.
1183 1197 request = get_current_request()
1184 1198 tmpl = safe_str(_gravatar_url)
1185 1199 tmpl = tmpl.replace('{email}', email_address)\
1186 1200 .replace('{md5email}', md5_safe(email_address.lower())) \
1187 1201 .replace('{netloc}', request.host)\
1188 1202 .replace('{scheme}', request.scheme)\
1189 1203 .replace('{size}', safe_str(size))
1190 1204 return tmpl
1191 1205 else:
1192 1206 return initials_gravatar(email_address, '', '', size=size)
1193 1207
1194 1208
1195 1209 class Page(_Page):
1196 1210 """
1197 1211 Custom pager to match rendering style with paginator
1198 1212 """
1199 1213
1200 1214 def _get_pos(self, cur_page, max_page, items):
1201 1215 edge = (items / 2) + 1
1202 1216 if (cur_page <= edge):
1203 1217 radius = max(items / 2, items - cur_page)
1204 1218 elif (max_page - cur_page) < edge:
1205 1219 radius = (items - 1) - (max_page - cur_page)
1206 1220 else:
1207 1221 radius = items / 2
1208 1222
1209 1223 left = max(1, (cur_page - (radius)))
1210 1224 right = min(max_page, cur_page + (radius))
1211 1225 return left, cur_page, right
1212 1226
1213 1227 def _range(self, regexp_match):
1214 1228 """
1215 1229 Return range of linked pages (e.g. '1 2 [3] 4 5 6 7 8').
1216 1230
1217 1231 Arguments:
1218 1232
1219 1233 regexp_match
1220 1234 A "re" (regular expressions) match object containing the
1221 1235 radius of linked pages around the current page in
1222 1236 regexp_match.group(1) as a string
1223 1237
1224 1238 This function is supposed to be called as a callable in
1225 1239 re.sub.
1226 1240
1227 1241 """
1228 1242 radius = int(regexp_match.group(1))
1229 1243
1230 1244 # Compute the first and last page number within the radius
1231 1245 # e.g. '1 .. 5 6 [7] 8 9 .. 12'
1232 1246 # -> leftmost_page = 5
1233 1247 # -> rightmost_page = 9
1234 1248 leftmost_page, _cur, rightmost_page = self._get_pos(self.page,
1235 1249 self.last_page,
1236 1250 (radius * 2) + 1)
1237 1251 nav_items = []
1238 1252
1239 1253 # Create a link to the first page (unless we are on the first page
1240 1254 # or there would be no need to insert '..' spacers)
1241 1255 if self.page != self.first_page and self.first_page < leftmost_page:
1242 1256 nav_items.append(self._pagerlink(self.first_page, self.first_page))
1243 1257
1244 1258 # Insert dots if there are pages between the first page
1245 1259 # and the currently displayed page range
1246 1260 if leftmost_page - self.first_page > 1:
1247 1261 # Wrap in a SPAN tag if nolink_attr is set
1248 1262 text = '..'
1249 1263 if self.dotdot_attr:
1250 1264 text = HTML.span(c=text, **self.dotdot_attr)
1251 1265 nav_items.append(text)
1252 1266
1253 1267 for thispage in xrange(leftmost_page, rightmost_page + 1):
1254 1268 # Hilight the current page number and do not use a link
1255 1269 if thispage == self.page:
1256 1270 text = '%s' % (thispage,)
1257 1271 # Wrap in a SPAN tag if nolink_attr is set
1258 1272 if self.curpage_attr:
1259 1273 text = HTML.span(c=text, **self.curpage_attr)
1260 1274 nav_items.append(text)
1261 1275 # Otherwise create just a link to that page
1262 1276 else:
1263 1277 text = '%s' % (thispage,)
1264 1278 nav_items.append(self._pagerlink(thispage, text))
1265 1279
1266 1280 # Insert dots if there are pages between the displayed
1267 1281 # page numbers and the end of the page range
1268 1282 if self.last_page - rightmost_page > 1:
1269 1283 text = '..'
1270 1284 # Wrap in a SPAN tag if nolink_attr is set
1271 1285 if self.dotdot_attr:
1272 1286 text = HTML.span(c=text, **self.dotdot_attr)
1273 1287 nav_items.append(text)
1274 1288
1275 1289 # Create a link to the very last page (unless we are on the last
1276 1290 # page or there would be no need to insert '..' spacers)
1277 1291 if self.page != self.last_page and rightmost_page < self.last_page:
1278 1292 nav_items.append(self._pagerlink(self.last_page, self.last_page))
1279 1293
1280 1294 ## prerender links
1281 1295 #_page_link = url.current()
1282 1296 #nav_items.append(literal('<link rel="prerender" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1283 1297 #nav_items.append(literal('<link rel="prefetch" href="%s?page=%s">' % (_page_link, str(int(self.page)+1))))
1284 1298 return self.separator.join(nav_items)
1285 1299
1286 1300 def pager(self, format='~2~', page_param='page', partial_param='partial',
1287 1301 show_if_single_page=False, separator=' ', onclick=None,
1288 1302 symbol_first='<<', symbol_last='>>',
1289 1303 symbol_previous='<', symbol_next='>',
1290 1304 link_attr={'class': 'pager_link', 'rel': 'prerender'},
1291 1305 curpage_attr={'class': 'pager_curpage'},
1292 1306 dotdot_attr={'class': 'pager_dotdot'}, **kwargs):
1293 1307
1294 1308 self.curpage_attr = curpage_attr
1295 1309 self.separator = separator
1296 1310 self.pager_kwargs = kwargs
1297 1311 self.page_param = page_param
1298 1312 self.partial_param = partial_param
1299 1313 self.onclick = onclick
1300 1314 self.link_attr = link_attr
1301 1315 self.dotdot_attr = dotdot_attr
1302 1316
1303 1317 # Don't show navigator if there is no more than one page
1304 1318 if self.page_count == 0 or (self.page_count == 1 and not show_if_single_page):
1305 1319 return ''
1306 1320
1307 1321 from string import Template
1308 1322 # Replace ~...~ in token format by range of pages
1309 1323 result = re.sub(r'~(\d+)~', self._range, format)
1310 1324
1311 1325 # Interpolate '%' variables
1312 1326 result = Template(result).safe_substitute({
1313 1327 'first_page': self.first_page,
1314 1328 'last_page': self.last_page,
1315 1329 'page': self.page,
1316 1330 'page_count': self.page_count,
1317 1331 'items_per_page': self.items_per_page,
1318 1332 'first_item': self.first_item,
1319 1333 'last_item': self.last_item,
1320 1334 'item_count': self.item_count,
1321 1335 'link_first': self.page > self.first_page and \
1322 1336 self._pagerlink(self.first_page, symbol_first) or '',
1323 1337 'link_last': self.page < self.last_page and \
1324 1338 self._pagerlink(self.last_page, symbol_last) or '',
1325 1339 'link_previous': self.previous_page and \
1326 1340 self._pagerlink(self.previous_page, symbol_previous) \
1327 1341 or HTML.span(symbol_previous, class_="pg-previous disabled"),
1328 1342 'link_next': self.next_page and \
1329 1343 self._pagerlink(self.next_page, symbol_next) \
1330 1344 or HTML.span(symbol_next, class_="pg-next disabled")
1331 1345 })
1332 1346
1333 1347 return literal(result)
1334 1348
1335 1349
1336 1350 #==============================================================================
1337 1351 # REPO PAGER, PAGER FOR REPOSITORY
1338 1352 #==============================================================================
1339 1353 class RepoPage(Page):
1340 1354
1341 1355 def __init__(self, collection, page=1, items_per_page=20,
1342 1356 item_count=None, url=None, **kwargs):
1343 1357
1344 1358 """Create a "RepoPage" instance. special pager for paging
1345 1359 repository
1346 1360 """
1347 1361 self._url_generator = url
1348 1362
1349 1363 # Safe the kwargs class-wide so they can be used in the pager() method
1350 1364 self.kwargs = kwargs
1351 1365
1352 1366 # Save a reference to the collection
1353 1367 self.original_collection = collection
1354 1368
1355 1369 self.collection = collection
1356 1370
1357 1371 # The self.page is the number of the current page.
1358 1372 # The first page has the number 1!
1359 1373 try:
1360 1374 self.page = int(page) # make it int() if we get it as a string
1361 1375 except (ValueError, TypeError):
1362 1376 self.page = 1
1363 1377
1364 1378 self.items_per_page = items_per_page
1365 1379
1366 1380 # Unless the user tells us how many items the collections has
1367 1381 # we calculate that ourselves.
1368 1382 if item_count is not None:
1369 1383 self.item_count = item_count
1370 1384 else:
1371 1385 self.item_count = len(self.collection)
1372 1386
1373 1387 # Compute the number of the first and last available page
1374 1388 if self.item_count > 0:
1375 1389 self.first_page = 1
1376 1390 self.page_count = int(math.ceil(float(self.item_count) /
1377 1391 self.items_per_page))
1378 1392 self.last_page = self.first_page + self.page_count - 1
1379 1393
1380 1394 # Make sure that the requested page number is the range of
1381 1395 # valid pages
1382 1396 if self.page > self.last_page:
1383 1397 self.page = self.last_page
1384 1398 elif self.page < self.first_page:
1385 1399 self.page = self.first_page
1386 1400
1387 1401 # Note: the number of items on this page can be less than
1388 1402 # items_per_page if the last page is not full
1389 1403 self.first_item = max(0, (self.item_count) - (self.page *
1390 1404 items_per_page))
1391 1405 self.last_item = ((self.item_count - 1) - items_per_page *
1392 1406 (self.page - 1))
1393 1407
1394 1408 self.items = list(self.collection[self.first_item:self.last_item + 1])
1395 1409
1396 1410 # Links to previous and next page
1397 1411 if self.page > self.first_page:
1398 1412 self.previous_page = self.page - 1
1399 1413 else:
1400 1414 self.previous_page = None
1401 1415
1402 1416 if self.page < self.last_page:
1403 1417 self.next_page = self.page + 1
1404 1418 else:
1405 1419 self.next_page = None
1406 1420
1407 1421 # No items available
1408 1422 else:
1409 1423 self.first_page = None
1410 1424 self.page_count = 0
1411 1425 self.last_page = None
1412 1426 self.first_item = None
1413 1427 self.last_item = None
1414 1428 self.previous_page = None
1415 1429 self.next_page = None
1416 1430 self.items = []
1417 1431
1418 1432 # This is a subclass of the 'list' type. Initialise the list now.
1419 1433 list.__init__(self, reversed(self.items))
1420 1434
1421 1435
1422 1436 def changed_tooltip(nodes):
1423 1437 """
1424 1438 Generates a html string for changed nodes in commit page.
1425 1439 It limits the output to 30 entries
1426 1440
1427 1441 :param nodes: LazyNodesGenerator
1428 1442 """
1429 1443 if nodes:
1430 1444 pref = ': <br/> '
1431 1445 suf = ''
1432 1446 if len(nodes) > 30:
1433 1447 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
1434 1448 return literal(pref + '<br/> '.join([safe_unicode(x.path)
1435 1449 for x in nodes[:30]]) + suf)
1436 1450 else:
1437 1451 return ': ' + _('No Files')
1438 1452
1439 1453
1440 1454 def breadcrumb_repo_link(repo):
1441 1455 """
1442 1456 Makes a breadcrumbs path link to repo
1443 1457
1444 1458 ex::
1445 1459 group >> subgroup >> repo
1446 1460
1447 1461 :param repo: a Repository instance
1448 1462 """
1449 1463
1450 1464 path = [
1451 1465 link_to(group.name, url('repo_group_home', group_name=group.group_name))
1452 1466 for group in repo.groups_with_parents
1453 1467 ] + [
1454 1468 link_to(repo.just_name, url('summary_home', repo_name=repo.repo_name))
1455 1469 ]
1456 1470
1457 1471 return literal(' &raquo; '.join(path))
1458 1472
1459 1473
1460 1474 def format_byte_size_binary(file_size):
1461 1475 """
1462 1476 Formats file/folder sizes to standard.
1463 1477 """
1464 1478 formatted_size = format_byte_size(file_size, binary=True)
1465 1479 return formatted_size
1466 1480
1467 1481
1468 1482 def fancy_file_stats(stats):
1469 1483 """
1470 1484 Displays a fancy two colored bar for number of added/deleted
1471 1485 lines of code on file
1472 1486
1473 1487 :param stats: two element list of added/deleted lines of code
1474 1488 """
1475 1489 from rhodecode.lib.diffs import NEW_FILENODE, DEL_FILENODE, \
1476 1490 MOD_FILENODE, RENAMED_FILENODE, CHMOD_FILENODE, BIN_FILENODE
1477 1491
1478 1492 def cgen(l_type, a_v, d_v):
1479 1493 mapping = {'tr': 'top-right-rounded-corner-mid',
1480 1494 'tl': 'top-left-rounded-corner-mid',
1481 1495 'br': 'bottom-right-rounded-corner-mid',
1482 1496 'bl': 'bottom-left-rounded-corner-mid'}
1483 1497 map_getter = lambda x: mapping[x]
1484 1498
1485 1499 if l_type == 'a' and d_v:
1486 1500 #case when added and deleted are present
1487 1501 return ' '.join(map(map_getter, ['tl', 'bl']))
1488 1502
1489 1503 if l_type == 'a' and not d_v:
1490 1504 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1491 1505
1492 1506 if l_type == 'd' and a_v:
1493 1507 return ' '.join(map(map_getter, ['tr', 'br']))
1494 1508
1495 1509 if l_type == 'd' and not a_v:
1496 1510 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
1497 1511
1498 1512 a, d = stats['added'], stats['deleted']
1499 1513 width = 100
1500 1514
1501 1515 if stats['binary']: # binary operations like chmod/rename etc
1502 1516 lbl = []
1503 1517 bin_op = 0 # undefined
1504 1518
1505 1519 # prefix with bin for binary files
1506 1520 if BIN_FILENODE in stats['ops']:
1507 1521 lbl += ['bin']
1508 1522
1509 1523 if NEW_FILENODE in stats['ops']:
1510 1524 lbl += [_('new file')]
1511 1525 bin_op = NEW_FILENODE
1512 1526 elif MOD_FILENODE in stats['ops']:
1513 1527 lbl += [_('mod')]
1514 1528 bin_op = MOD_FILENODE
1515 1529 elif DEL_FILENODE in stats['ops']:
1516 1530 lbl += [_('del')]
1517 1531 bin_op = DEL_FILENODE
1518 1532 elif RENAMED_FILENODE in stats['ops']:
1519 1533 lbl += [_('rename')]
1520 1534 bin_op = RENAMED_FILENODE
1521 1535
1522 1536 # chmod can go with other operations, so we add a + to lbl if needed
1523 1537 if CHMOD_FILENODE in stats['ops']:
1524 1538 lbl += [_('chmod')]
1525 1539 if bin_op == 0:
1526 1540 bin_op = CHMOD_FILENODE
1527 1541
1528 1542 lbl = '+'.join(lbl)
1529 1543 b_a = '<div class="bin bin%s %s" style="width:100%%">%s</div>' \
1530 1544 % (bin_op, cgen('a', a_v='', d_v=0), lbl)
1531 1545 b_d = '<div class="bin bin1" style="width:0%%"></div>'
1532 1546 return literal('<div style="width:%spx">%s%s</div>' % (width, b_a, b_d))
1533 1547
1534 1548 t = stats['added'] + stats['deleted']
1535 1549 unit = float(width) / (t or 1)
1536 1550
1537 1551 # needs > 9% of width to be visible or 0 to be hidden
1538 1552 a_p = max(9, unit * a) if a > 0 else 0
1539 1553 d_p = max(9, unit * d) if d > 0 else 0
1540 1554 p_sum = a_p + d_p
1541 1555
1542 1556 if p_sum > width:
1543 1557 #adjust the percentage to be == 100% since we adjusted to 9
1544 1558 if a_p > d_p:
1545 1559 a_p = a_p - (p_sum - width)
1546 1560 else:
1547 1561 d_p = d_p - (p_sum - width)
1548 1562
1549 1563 a_v = a if a > 0 else ''
1550 1564 d_v = d if d > 0 else ''
1551 1565
1552 1566 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
1553 1567 cgen('a', a_v, d_v), a_p, a_v
1554 1568 )
1555 1569 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
1556 1570 cgen('d', a_v, d_v), d_p, d_v
1557 1571 )
1558 1572 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
1559 1573
1560 1574
1561 1575 def urlify_text(text_, safe=True):
1562 1576 """
1563 1577 Extrac urls from text and make html links out of them
1564 1578
1565 1579 :param text_:
1566 1580 """
1567 1581
1568 1582 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@#.&+]'''
1569 1583 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
1570 1584
1571 1585 def url_func(match_obj):
1572 1586 url_full = match_obj.groups()[0]
1573 1587 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
1574 1588 _newtext = url_pat.sub(url_func, text_)
1575 1589 if safe:
1576 1590 return literal(_newtext)
1577 1591 return _newtext
1578 1592
1579 1593
1580 1594 def urlify_commits(text_, repository):
1581 1595 """
1582 1596 Extract commit ids from text and make link from them
1583 1597
1584 1598 :param text_:
1585 1599 :param repository: repo name to build the URL with
1586 1600 """
1587 1601 from pylons import url # doh, we need to re-import url to mock it later
1588 1602 URL_PAT = re.compile(r'(^|\s)([0-9a-fA-F]{12,40})($|\s)')
1589 1603
1590 1604 def url_func(match_obj):
1591 1605 commit_id = match_obj.groups()[1]
1592 1606 pref = match_obj.groups()[0]
1593 1607 suf = match_obj.groups()[2]
1594 1608
1595 1609 tmpl = (
1596 1610 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1597 1611 '%(commit_id)s</a>%(suf)s'
1598 1612 )
1599 1613 return tmpl % {
1600 1614 'pref': pref,
1601 1615 'cls': 'revision-link',
1602 1616 'url': url('changeset_home', repo_name=repository,
1603 1617 revision=commit_id),
1604 1618 'commit_id': commit_id,
1605 1619 'suf': suf
1606 1620 }
1607 1621
1608 1622 newtext = URL_PAT.sub(url_func, text_)
1609 1623
1610 1624 return newtext
1611 1625
1612 1626
1613 1627 def _process_url_func(match_obj, repo_name, uid, entry):
1614 1628 pref = ''
1615 1629 if match_obj.group().startswith(' '):
1616 1630 pref = ' '
1617 1631
1618 1632 issue_id = ''.join(match_obj.groups())
1619 1633 tmpl = (
1620 1634 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1621 1635 '%(issue-prefix)s%(id-repr)s'
1622 1636 '</a>')
1623 1637
1624 1638 (repo_name_cleaned,
1625 1639 parent_group_name) = RepoGroupModel().\
1626 1640 _get_group_name_and_parent(repo_name)
1627 1641
1628 1642 # variables replacement
1629 1643 named_vars = {
1630 1644 'id': issue_id,
1631 1645 'repo': repo_name,
1632 1646 'repo_name': repo_name_cleaned,
1633 1647 'group_name': parent_group_name
1634 1648 }
1635 1649 # named regex variables
1636 1650 named_vars.update(match_obj.groupdict())
1637 1651 _url = string.Template(entry['url']).safe_substitute(**named_vars)
1638 1652
1639 1653 return tmpl % {
1640 1654 'pref': pref,
1641 1655 'cls': 'issue-tracker-link',
1642 1656 'url': _url,
1643 1657 'id-repr': issue_id,
1644 1658 'issue-prefix': entry['pref'],
1645 1659 'serv': entry['url'],
1646 1660 }
1647 1661
1648 1662
1649 1663 def process_patterns(text_string, repo_name, config):
1650 1664 repo = None
1651 1665 if repo_name:
1652 1666 # Retrieving repo_name to avoid invalid repo_name to explode on
1653 1667 # IssueTrackerSettingsModel but still passing invalid name further down
1654 1668 repo = Repository.get_by_repo_name(repo_name, cache=True)
1655 1669
1656 1670 settings_model = IssueTrackerSettingsModel(repo=repo)
1657 1671 active_entries = settings_model.get_settings(cache=True)
1658 1672
1659 1673 newtext = text_string
1660 1674 for uid, entry in active_entries.items():
1661 1675 url_func = partial(
1662 1676 _process_url_func, repo_name=repo_name, entry=entry, uid=uid)
1663 1677
1664 1678 log.debug('found issue tracker entry with uid %s' % (uid,))
1665 1679
1666 1680 if not (entry['pat'] and entry['url']):
1667 1681 log.debug('skipping due to missing data')
1668 1682 continue
1669 1683
1670 1684 log.debug('issue tracker entry: uid: `%s` PAT:%s URL:%s PREFIX:%s'
1671 1685 % (uid, entry['pat'], entry['url'], entry['pref']))
1672 1686
1673 1687 try:
1674 1688 pattern = re.compile(r'%s' % entry['pat'])
1675 1689 except re.error:
1676 1690 log.exception(
1677 1691 'issue tracker pattern: `%s` failed to compile',
1678 1692 entry['pat'])
1679 1693 continue
1680 1694
1681 1695 newtext = pattern.sub(url_func, newtext)
1682 1696 log.debug('processed prefix:uid `%s`' % (uid,))
1683 1697
1684 1698 return newtext
1685 1699
1686 1700
1687 1701 def urlify_commit_message(commit_text, repository=None):
1688 1702 """
1689 1703 Parses given text message and makes proper links.
1690 1704 issues are linked to given issue-server, and rest is a commit link
1691 1705
1692 1706 :param commit_text:
1693 1707 :param repository:
1694 1708 """
1695 1709 from pylons import url # doh, we need to re-import url to mock it later
1696 1710 from rhodecode import CONFIG
1697 1711
1698 1712 def escaper(string):
1699 1713 return string.replace('<', '&lt;').replace('>', '&gt;')
1700 1714
1701 1715 newtext = escaper(commit_text)
1702 1716 # urlify commits - extract commit ids and make link out of them, if we have
1703 1717 # the scope of repository present.
1704 1718 if repository:
1705 1719 newtext = urlify_commits(newtext, repository)
1706 1720
1707 1721 # extract http/https links and make them real urls
1708 1722 newtext = urlify_text(newtext, safe=False)
1709 1723
1710 1724 # process issue tracker patterns
1711 1725 newtext = process_patterns(newtext, repository or '', CONFIG)
1712 1726
1713 1727 return literal(newtext)
1714 1728
1715 1729
1716 1730 def rst(source, mentions=False):
1717 1731 return literal('<div class="rst-block">%s</div>' %
1718 1732 MarkupRenderer.rst(source, mentions=mentions))
1719 1733
1720 1734
1721 1735 def markdown(source, mentions=False):
1722 1736 return literal('<div class="markdown-block">%s</div>' %
1723 1737 MarkupRenderer.markdown(source, flavored=True,
1724 1738 mentions=mentions))
1725 1739
1726 1740 def renderer_from_filename(filename, exclude=None):
1727 1741 return MarkupRenderer.renderer_from_filename(filename, exclude=exclude)
1728 1742
1729 1743
1730 1744 def render(source, renderer='rst', mentions=False):
1731 1745 if renderer == 'rst':
1732 1746 return rst(source, mentions=mentions)
1733 1747 if renderer == 'markdown':
1734 1748 return markdown(source, mentions=mentions)
1735 1749
1736 1750
1737 1751 def commit_status(repo, commit_id):
1738 1752 return ChangesetStatusModel().get_status(repo, commit_id)
1739 1753
1740 1754
1741 1755 def commit_status_lbl(commit_status):
1742 1756 return dict(ChangesetStatus.STATUSES).get(commit_status)
1743 1757
1744 1758
1745 1759 def commit_time(repo_name, commit_id):
1746 1760 repo = Repository.get_by_repo_name(repo_name)
1747 1761 commit = repo.get_commit(commit_id=commit_id)
1748 1762 return commit.date
1749 1763
1750 1764
1751 1765 def get_permission_name(key):
1752 1766 return dict(Permission.PERMS).get(key)
1753 1767
1754 1768
1755 1769 def journal_filter_help():
1756 1770 return _(
1757 1771 'Example filter terms:\n' +
1758 1772 ' repository:vcs\n' +
1759 1773 ' username:marcin\n' +
1760 1774 ' action:*push*\n' +
1761 1775 ' ip:127.0.0.1\n' +
1762 1776 ' date:20120101\n' +
1763 1777 ' date:[20120101100000 TO 20120102]\n' +
1764 1778 '\n' +
1765 1779 'Generate wildcards using \'*\' character:\n' +
1766 1780 ' "repository:vcs*" - search everything starting with \'vcs\'\n' +
1767 1781 ' "repository:*vcs*" - search for repository containing \'vcs\'\n' +
1768 1782 '\n' +
1769 1783 'Optional AND / OR operators in queries\n' +
1770 1784 ' "repository:vcs OR repository:test"\n' +
1771 1785 ' "username:test AND repository:test*"\n'
1772 1786 )
1773 1787
1774 1788
1775 1789 def not_mapped_error(repo_name):
1776 1790 flash(_('%s repository is not mapped to db perhaps'
1777 1791 ' it was created or renamed from the filesystem'
1778 1792 ' please run the application again'
1779 1793 ' in order to rescan repositories') % repo_name, category='error')
1780 1794
1781 1795
1782 1796 def ip_range(ip_addr):
1783 1797 from rhodecode.model.db import UserIpMap
1784 1798 s, e = UserIpMap._get_ip_range(ip_addr)
1785 1799 return '%s - %s' % (s, e)
1786 1800
1787 1801
1788 1802 def form(url, method='post', needs_csrf_token=True, **attrs):
1789 1803 """Wrapper around webhelpers.tags.form to prevent CSRF attacks."""
1790 1804 if method.lower() != 'get' and needs_csrf_token:
1791 1805 raise Exception(
1792 1806 'Forms to POST/PUT/DELETE endpoints should have (in general) a ' +
1793 1807 'CSRF token. If the endpoint does not require such token you can ' +
1794 1808 'explicitly set the parameter needs_csrf_token to false.')
1795 1809
1796 1810 return wh_form(url, method=method, **attrs)
1797 1811
1798 1812
1799 1813 def secure_form(url, method="POST", multipart=False, **attrs):
1800 1814 """Start a form tag that points the action to an url. This
1801 1815 form tag will also include the hidden field containing
1802 1816 the auth token.
1803 1817
1804 1818 The url options should be given either as a string, or as a
1805 1819 ``url()`` function. The method for the form defaults to POST.
1806 1820
1807 1821 Options:
1808 1822
1809 1823 ``multipart``
1810 1824 If set to True, the enctype is set to "multipart/form-data".
1811 1825 ``method``
1812 1826 The method to use when submitting the form, usually either
1813 1827 "GET" or "POST". If "PUT", "DELETE", or another verb is used, a
1814 1828 hidden input with name _method is added to simulate the verb
1815 1829 over POST.
1816 1830
1817 1831 """
1818 1832 from webhelpers.pylonslib.secure_form import insecure_form
1819 1833 from rhodecode.lib.auth import get_csrf_token, csrf_token_key
1820 1834 form = insecure_form(url, method, multipart, **attrs)
1821 1835 token = HTML.div(hidden(csrf_token_key, get_csrf_token()), style="display: none;")
1822 1836 return literal("%s\n%s" % (form, token))
1823 1837
1824 1838 def dropdownmenu(name, selected, options, enable_filter=False, **attrs):
1825 1839 select_html = select(name, selected, options, **attrs)
1826 1840 select2 = """
1827 1841 <script>
1828 1842 $(document).ready(function() {
1829 1843 $('#%s').select2({
1830 1844 containerCssClass: 'drop-menu',
1831 1845 dropdownCssClass: 'drop-menu-dropdown',
1832 1846 dropdownAutoWidth: true%s
1833 1847 });
1834 1848 });
1835 1849 </script>
1836 1850 """
1837 1851 filter_option = """,
1838 1852 minimumResultsForSearch: -1
1839 1853 """
1840 1854 input_id = attrs.get('id') or name
1841 1855 filter_enabled = "" if enable_filter else filter_option
1842 1856 select_script = literal(select2 % (input_id, filter_enabled))
1843 1857
1844 1858 return literal(select_html+select_script)
1845 1859
1846 1860
1847 1861 def get_visual_attr(tmpl_context_var, attr_name):
1848 1862 """
1849 1863 A safe way to get a variable from visual variable of template context
1850 1864
1851 1865 :param tmpl_context_var: instance of tmpl_context, usually present as `c`
1852 1866 :param attr_name: name of the attribute we fetch from the c.visual
1853 1867 """
1854 1868 visual = getattr(tmpl_context_var, 'visual', None)
1855 1869 if not visual:
1856 1870 return
1857 1871 else:
1858 1872 return getattr(visual, attr_name, None)
1859 1873
1860 1874
1861 1875 def get_last_path_part(file_node):
1862 1876 if not file_node.path:
1863 1877 return u''
1864 1878
1865 1879 path = safe_unicode(file_node.path.split('/')[-1])
1866 1880 return u'../' + path
1867 1881
1868 1882
1869 1883 def route_path(*args, **kwds):
1870 1884 """
1871 1885 Wrapper around pyramids `route_path` function. It is used to generate
1872 1886 URLs from within pylons views or templates. This will be removed when
1873 1887 pyramid migration if finished.
1874 1888 """
1875 1889 req = get_current_request()
1876 1890 return req.route_path(*args, **kwds)
1877 1891
1878 1892
1879 1893 def resource_path(*args, **kwds):
1880 1894 """
1881 1895 Wrapper around pyramids `route_path` function. It is used to generate
1882 1896 URLs from within pylons views or templates. This will be removed when
1883 1897 pyramid migration if finished.
1884 1898 """
1885 1899 req = get_current_request()
1886 1900 return req.resource_path(*args, **kwds)
@@ -1,2095 +1,2093 b''
1 1 //Primary CSS
2 2
3 3 //--- IMPORTS ------------------//
4 4
5 5 @import 'helpers';
6 6 @import 'mixins';
7 7 @import 'rcicons';
8 8 @import 'fonts';
9 9 @import 'variables';
10 10 @import 'bootstrap-variables';
11 11 @import 'form-bootstrap';
12 12 @import 'codemirror';
13 13 @import 'legacy_code_styles';
14 14 @import 'progress-bar';
15 15
16 16 @import 'type';
17 17 @import 'alerts';
18 18 @import 'buttons';
19 19 @import 'tags';
20 20 @import 'code-block';
21 21 @import 'examples';
22 22 @import 'login';
23 23 @import 'main-content';
24 24 @import 'select2';
25 25 @import 'comments';
26 26 @import 'panels-bootstrap';
27 27 @import 'panels';
28 28
29 29
30 30 //--- BASE ------------------//
31 31 .noscript-error {
32 32 top: 0;
33 33 left: 0;
34 34 width: 100%;
35 35 z-index: 101;
36 36 text-align: center;
37 37 font-family: @text-semibold;
38 38 font-size: 120%;
39 39 color: white;
40 40 background-color: @alert2;
41 41 padding: 5px 0 5px 0;
42 42 }
43 43
44 44 html {
45 45 display: table;
46 46 height: 100%;
47 47 width: 100%;
48 48 }
49 49
50 50 body {
51 51 display: table-cell;
52 52 width: 100%;
53 53 }
54 54
55 55 //--- LAYOUT ------------------//
56 56
57 57 .hidden{
58 58 display: none !important;
59 59 }
60 60
61 61 .box{
62 62 float: left;
63 63 width: 100%;
64 64 }
65 65
66 66 .browser-header {
67 67 clear: both;
68 68 }
69 69 .main {
70 70 clear: both;
71 71 padding:0 0 @pagepadding;
72 72 height: auto;
73 73
74 74 &:after { //clearfix
75 75 content:"";
76 76 clear:both;
77 77 width:100%;
78 78 display:block;
79 79 }
80 80 }
81 81
82 82 .action-link{
83 83 margin-left: @padding;
84 84 padding-left: @padding;
85 85 border-left: @border-thickness solid @border-default-color;
86 86 }
87 87
88 88 input + .action-link, .action-link.first{
89 89 border-left: none;
90 90 }
91 91
92 92 .action-link.last{
93 93 margin-right: @padding;
94 94 padding-right: @padding;
95 95 }
96 96
97 97 .action-link.active,
98 98 .action-link.active a{
99 99 color: @grey4;
100 100 }
101 101
102 102 ul.simple-list{
103 103 list-style: none;
104 104 margin: 0;
105 105 padding: 0;
106 106 }
107 107
108 108 .main-content {
109 109 padding-bottom: @pagepadding;
110 110 }
111 111
112 112 .wrapper {
113 113 position: relative;
114 114 max-width: @wrapper-maxwidth;
115 115 margin: 0 auto;
116 116 }
117 117
118 118 #content {
119 119 clear: both;
120 120 padding: 0 @contentpadding;
121 121 }
122 122
123 123 .advanced-settings-fields{
124 124 input{
125 125 margin-left: @textmargin;
126 126 margin-right: @padding/2;
127 127 }
128 128 }
129 129
130 130 .cs_files_title {
131 131 margin: @pagepadding 0 0;
132 132 }
133 133
134 134 input.inline[type="file"] {
135 135 display: inline;
136 136 }
137 137
138 138 .error_page {
139 139 margin: 10% auto;
140 140
141 141 h1 {
142 142 color: @grey2;
143 143 }
144 144
145 145 .error-branding {
146 146 font-family: @text-semibold;
147 147 color: @grey4;
148 148 }
149 149
150 150 .error_message {
151 151 font-family: @text-regular;
152 152 }
153 153
154 154 .sidebar {
155 155 min-height: 275px;
156 156 margin: 0;
157 157 padding: 0 0 @sidebarpadding @sidebarpadding;
158 158 border: none;
159 159 }
160 160
161 161 .main-content {
162 162 position: relative;
163 163 margin: 0 @sidebarpadding @sidebarpadding;
164 164 padding: 0 0 0 @sidebarpadding;
165 165 border-left: @border-thickness solid @grey5;
166 166
167 167 @media (max-width:767px) {
168 168 clear: both;
169 169 width: 100%;
170 170 margin: 0;
171 171 border: none;
172 172 }
173 173 }
174 174
175 175 .inner-column {
176 176 float: left;
177 177 width: 29.75%;
178 178 min-height: 150px;
179 179 margin: @sidebarpadding 2% 0 0;
180 180 padding: 0 2% 0 0;
181 181 border-right: @border-thickness solid @grey5;
182 182
183 183 @media (max-width:767px) {
184 184 clear: both;
185 185 width: 100%;
186 186 border: none;
187 187 }
188 188
189 189 ul {
190 190 padding-left: 1.25em;
191 191 }
192 192
193 193 &:last-child {
194 194 margin: @sidebarpadding 0 0;
195 195 border: none;
196 196 }
197 197
198 198 h4 {
199 199 margin: 0 0 @padding;
200 200 font-family: @text-semibold;
201 201 }
202 202 }
203 203 }
204 204 .error-page-logo {
205 205 width: 130px;
206 206 height: 160px;
207 207 }
208 208
209 209 // HEADER
210 210 .header {
211 211
212 212 // TODO: johbo: Fix login pages, so that they work without a min-height
213 213 // for the header and then remove the min-height. I chose a smaller value
214 214 // intentionally here to avoid rendering issues in the main navigation.
215 215 min-height: 49px;
216 216
217 217 position: relative;
218 218 vertical-align: bottom;
219 219 padding: 0 @header-padding;
220 220 background-color: @grey2;
221 221 color: @grey5;
222 222
223 223 .title {
224 224 overflow: visible;
225 225 }
226 226
227 227 &:before,
228 228 &:after {
229 229 content: "";
230 230 clear: both;
231 231 width: 100%;
232 232 }
233 233
234 234 // TODO: johbo: Avoids breaking "Repositories" chooser
235 235 .select2-container .select2-choice .select2-arrow {
236 236 display: none;
237 237 }
238 238 }
239 239
240 240 #header-inner {
241 241 &.title {
242 242 margin: 0;
243 243 }
244 244 &:before,
245 245 &:after {
246 246 content: "";
247 247 clear: both;
248 248 }
249 249 }
250 250
251 251 // Gists
252 252 #files_data {
253 253 clear: both; //for firefox
254 254 }
255 255 #gistid {
256 256 margin-right: @padding;
257 257 }
258 258
259 259 // Global Settings Editor
260 260 .textarea.editor {
261 261 float: left;
262 262 position: relative;
263 263 max-width: @texteditor-width;
264 264
265 265 select {
266 266 position: absolute;
267 267 top:10px;
268 268 right:0;
269 269 }
270 270
271 271 .CodeMirror {
272 272 margin: 0;
273 273 }
274 274
275 275 .help-block {
276 276 margin: 0 0 @padding;
277 277 padding:.5em;
278 278 background-color: @grey6;
279 279 }
280 280 }
281 281
282 282 ul.auth_plugins {
283 283 margin: @padding 0 @padding @legend-width;
284 284 padding: 0;
285 285
286 286 li {
287 287 margin-bottom: @padding;
288 288 line-height: 1em;
289 289 list-style-type: none;
290 290
291 291 .auth_buttons .btn {
292 292 margin-right: @padding;
293 293 }
294 294
295 295 &:before { content: none; }
296 296 }
297 297 }
298 298
299 299
300 300 // My Account PR list
301 301
302 302 #show_closed {
303 303 margin: 0 1em 0 0;
304 304 }
305 305
306 306 .pullrequestlist {
307 307 .closed {
308 308 background-color: @grey6;
309 309 }
310 310 .td-status {
311 311 padding-left: .5em;
312 312 }
313 313 .truncate {
314 314 height: 2.75em;
315 315 white-space: pre-line;
316 316 }
317 317 table.rctable .user {
318 318 padding-left: 0;
319 319 }
320 320 }
321 321
322 322 // Pull Requests
323 323
324 324 .pullrequests_section_head {
325 325 display: block;
326 326 clear: both;
327 327 margin: @padding 0;
328 328 font-family: @text-bold;
329 329 }
330 330
331 331 .pr-origininfo, .pr-targetinfo {
332 332 position: relative;
333 333
334 334 .tag {
335 335 display: inline-block;
336 336 margin: 0 1em .5em 0;
337 337 }
338 338
339 339 .clone-url {
340 340 display: inline-block;
341 341 margin: 0 0 .5em 0;
342 342 padding: 0;
343 343 line-height: 1.2em;
344 344 }
345 345 }
346 346
347 347 .pr-pullinfo {
348 348 clear: both;
349 349 margin: .5em 0;
350 350 }
351 351
352 352 #pr-title-input {
353 353 width: 72%;
354 354 font-size: 1em;
355 355 font-family: @text-bold;
356 356 margin: 0;
357 357 padding: 0 0 0 @padding/4;
358 358 line-height: 1.7em;
359 359 color: @text-color;
360 360 letter-spacing: .02em;
361 361 }
362 362
363 363 #pullrequest_title {
364 364 width: 100%;
365 365 box-sizing: border-box;
366 366 }
367 367
368 368 #pr_open_message {
369 369 border: @border-thickness solid #fff;
370 370 border-radius: @border-radius;
371 371 padding: @padding-large-vertical @padding-large-vertical @padding-large-vertical 0;
372 372 text-align: right;
373 373 overflow: hidden;
374 374 }
375 375
376 376 .pr-submit-button {
377 377 float: right;
378 378 margin: 0 0 0 5px;
379 379 }
380 380
381 381 .pr-spacing-container {
382 382 padding: 20px;
383 383 clear: both
384 384 }
385 385
386 386 #pr-description-input {
387 387 margin-bottom: 0;
388 388 }
389 389
390 390 .pr-description-label {
391 391 vertical-align: top;
392 392 }
393 393
394 394 .perms_section_head {
395 395 min-width: 625px;
396 396
397 397 h2 {
398 398 margin-bottom: 0;
399 399 }
400 400
401 401 .label-checkbox {
402 402 float: left;
403 403 }
404 404
405 405 &.field {
406 406 margin: @space 0 @padding;
407 407 }
408 408
409 409 &:first-child.field {
410 410 margin-top: 0;
411 411
412 412 .label {
413 413 margin-top: 0;
414 414 padding-top: 0;
415 415 }
416 416
417 417 .radios {
418 418 padding-top: 0;
419 419 }
420 420 }
421 421
422 422 .radios {
423 423 float: right;
424 424 position: relative;
425 425 width: 405px;
426 426 }
427 427 }
428 428
429 429 //--- MODULES ------------------//
430 430
431 431
432 432 // Fixed Sidebar Column
433 433 .sidebar-col-wrapper {
434 434 padding-left: @sidebar-all-width;
435 435
436 436 .sidebar {
437 437 width: @sidebar-width;
438 438 margin-left: -@sidebar-all-width;
439 439 }
440 440 }
441 441
442 442 .sidebar-col-wrapper.scw-small {
443 443 padding-left: @sidebar-small-all-width;
444 444
445 445 .sidebar {
446 446 width: @sidebar-small-width;
447 447 margin-left: -@sidebar-small-all-width;
448 448 }
449 449 }
450 450
451 451
452 452 // FOOTER
453 453 #footer {
454 454 padding: 0;
455 455 text-align: center;
456 456 vertical-align: middle;
457 457 color: @grey2;
458 458 background-color: @grey6;
459 459
460 460 p {
461 461 margin: 0;
462 462 padding: 1em;
463 463 line-height: 1em;
464 464 }
465 465
466 466 .server-instance { //server instance
467 467 display: none;
468 468 }
469 469
470 470 .title {
471 471 float: none;
472 472 margin: 0 auto;
473 473 }
474 474 }
475 475
476 476 button.close {
477 477 padding: 0;
478 478 cursor: pointer;
479 479 background: transparent;
480 480 border: 0;
481 481 .box-shadow(none);
482 482 -webkit-appearance: none;
483 483 }
484 484
485 485 .close {
486 486 float: right;
487 487 font-size: 21px;
488 488 font-family: @text-bootstrap;
489 489 line-height: 1em;
490 490 font-weight: bold;
491 491 color: @grey2;
492 492
493 493 &:hover,
494 494 &:focus {
495 495 color: @grey1;
496 496 text-decoration: none;
497 497 cursor: pointer;
498 498 }
499 499 }
500 500
501 501 // GRID
502 502 .sorting,
503 503 .sorting_desc,
504 504 .sorting_asc {
505 505 cursor: pointer;
506 506 }
507 507 .sorting_desc:after {
508 508 content: "\00A0\25B2";
509 509 font-size: .75em;
510 510 }
511 511 .sorting_asc:after {
512 512 content: "\00A0\25BC";
513 513 font-size: .68em;
514 514 }
515 515
516 516
517 517 .user_auth_tokens {
518 518
519 519 &.truncate {
520 520 white-space: nowrap;
521 521 overflow: hidden;
522 522 text-overflow: ellipsis;
523 523 }
524 524
525 525 .fields .field .input {
526 526 margin: 0;
527 527 }
528 528
529 529 input#description {
530 530 width: 100px;
531 531 margin: 0;
532 532 }
533 533
534 534 .drop-menu {
535 535 // TODO: johbo: Remove this, should work out of the box when
536 536 // having multiple inputs inline
537 537 margin: 0 0 0 5px;
538 538 }
539 539 }
540 540 #user_list_table {
541 541 .closed {
542 542 background-color: @grey6;
543 543 }
544 544 }
545 545
546 546
547 547 input {
548 548 &.disabled {
549 549 opacity: .5;
550 550 }
551 551 }
552 552
553 553 // remove extra padding in firefox
554 554 input::-moz-focus-inner { border:0; padding:0 }
555 555
556 556 .adjacent input {
557 557 margin-bottom: @padding;
558 558 }
559 559
560 560 .permissions_boxes {
561 561 display: block;
562 562 }
563 563
564 564 //TODO: lisa: this should be in tables
565 565 .show_more_col {
566 566 width: 20px;
567 567 }
568 568
569 569 //FORMS
570 570
571 571 .medium-inline,
572 572 input#description.medium-inline {
573 573 display: inline;
574 574 width: @medium-inline-input-width;
575 575 min-width: 100px;
576 576 }
577 577
578 578 select {
579 579 //reset
580 580 -webkit-appearance: none;
581 581 -moz-appearance: none;
582 582
583 583 display: inline-block;
584 584 height: 28px;
585 585 width: auto;
586 586 margin: 0 @padding @padding 0;
587 587 padding: 0 18px 0 8px;
588 588 line-height:1em;
589 589 font-size: @basefontsize;
590 590 border: @border-thickness solid @rcblue;
591 591 background:white url("../images/dt-arrow-dn.png") no-repeat 100% 50%;
592 592 color: @rcblue;
593 593
594 594 &:after {
595 595 content: "\00A0\25BE";
596 596 }
597 597
598 598 &:focus {
599 599 outline: none;
600 600 }
601 601 }
602 602
603 603 option {
604 604 &:focus {
605 605 outline: none;
606 606 }
607 607 }
608 608
609 609 input,
610 610 textarea {
611 611 padding: @input-padding;
612 612 border: @input-border-thickness solid @border-highlight-color;
613 613 .border-radius (@border-radius);
614 614 font-family: @text-light;
615 615 font-size: @basefontsize;
616 616
617 617 &.input-sm {
618 618 padding: 5px;
619 619 }
620 620
621 621 &#description {
622 622 min-width: @input-description-minwidth;
623 623 min-height: 1em;
624 624 padding: 10px;
625 625 }
626 626 }
627 627
628 628 .field-sm {
629 629 input,
630 630 textarea {
631 631 padding: 5px;
632 632 }
633 633 }
634 634
635 635 textarea {
636 636 display: block;
637 637 clear: both;
638 638 width: 100%;
639 639 min-height: 100px;
640 640 margin-bottom: @padding;
641 641 .box-sizing(border-box);
642 642 overflow: auto;
643 643 }
644 644
645 645 label {
646 646 font-family: @text-light;
647 647 }
648 648
649 649 // GRAVATARS
650 650 // centers gravatar on username to the right
651 651
652 652 .gravatar {
653 653 display: inline;
654 654 min-width: 16px;
655 655 min-height: 16px;
656 656 margin: -5px 0;
657 657 padding: 0;
658 658 line-height: 1em;
659 659 border: 1px solid @grey4;
660 660
661 661 &.gravatar-large {
662 662 margin: -0.5em .25em -0.5em 0;
663 663 }
664 664
665 665 & + .user {
666 666 display: inline;
667 667 margin: 0;
668 668 padding: 0 0 0 .17em;
669 669 line-height: 1em;
670 670 }
671 671 }
672 672
673 .user-inline-data {
674 display: inline-block;
675 float: left;
676 padding-left: .5em;
677 line-height: 1.3em;
678 }
679
673 680 .rc-user { // gravatar + user wrapper
674 681 float: left;
675 682 position: relative;
676 683 min-width: 100px;
677 684 max-width: 200px;
678 685 min-height: (@gravatar-size + @border-thickness * 2); // account for border
679 686 display: block;
680 687 padding: 0 0 0 (@gravatar-size + @basefontsize/2 + @border-thickness * 2);
681 688
682 689
683 690 .gravatar {
684 691 display: block;
685 692 position: absolute;
686 693 top: 0;
687 694 left: 0;
688 695 min-width: @gravatar-size;
689 696 min-height: @gravatar-size;
690 697 margin: 0;
691 698 }
692 699
693 700 .user {
694 701 display: block;
695 702 max-width: 175px;
696 703 padding-top: 2px;
697 704 overflow: hidden;
698 705 text-overflow: ellipsis;
699 706 }
700 707 }
701 708
702 709 .gist-gravatar,
703 710 .journal_container {
704 711 .gravatar-large {
705 712 margin: 0 .5em -10px 0;
706 713 }
707 714 }
708 715
709 716
710 717 // ADMIN SETTINGS
711 718
712 719 // Tag Patterns
713 720 .tag_patterns {
714 721 .tag_input {
715 722 margin-bottom: @padding;
716 723 }
717 724 }
718 725
719 726 .locked_input {
720 727 position: relative;
721 728
722 729 input {
723 730 display: inline;
724 731 margin-top: 3px;
725 732 }
726 733
727 734 br {
728 735 display: none;
729 736 }
730 737
731 738 .error-message {
732 739 float: left;
733 740 width: 100%;
734 741 }
735 742
736 743 .lock_input_button {
737 744 display: inline;
738 745 }
739 746
740 747 .help-block {
741 748 clear: both;
742 749 }
743 750 }
744 751
745 752 // Notifications
746 753
747 754 .notifications_buttons {
748 755 margin: 0 0 @space 0;
749 756 padding: 0;
750 757
751 758 .btn {
752 759 display: inline-block;
753 760 }
754 761 }
755 762
756 763 .notification-list {
757 764
758 765 div {
759 766 display: inline-block;
760 767 vertical-align: middle;
761 768 }
762 769
763 770 .container {
764 771 display: block;
765 772 margin: 0 0 @padding 0;
766 773 }
767 774
768 775 .delete-notifications {
769 776 margin-left: @padding;
770 777 text-align: right;
771 778 cursor: pointer;
772 779 }
773 780
774 781 .read-notifications {
775 782 margin-left: @padding/2;
776 783 text-align: right;
777 784 width: 35px;
778 785 cursor: pointer;
779 786 }
780 787
781 788 .icon-minus-sign {
782 789 color: @alert2;
783 790 }
784 791
785 792 .icon-ok-sign {
786 793 color: @alert1;
787 794 }
788 795 }
789 796
790 797 .user_settings {
791 798 float: left;
792 799 clear: both;
793 800 display: block;
794 801 width: 100%;
795 802
796 803 .gravatar_box {
797 804 margin-bottom: @padding;
798 805
799 806 &:after {
800 807 content: " ";
801 808 clear: both;
802 809 width: 100%;
803 810 }
804 811 }
805 812
806 813 .fields .field {
807 814 clear: both;
808 815 }
809 816 }
810 817
811 818 .advanced_settings {
812 819 margin-bottom: @space;
813 820
814 821 .help-block {
815 822 margin-left: 0;
816 823 }
817 824
818 825 button + .help-block {
819 826 margin-top: @padding;
820 827 }
821 828 }
822 829
823 830 // admin settings radio buttons and labels
824 831 .label-2 {
825 832 float: left;
826 833 width: @label2-width;
827 834
828 835 label {
829 836 color: @grey1;
830 837 }
831 838 }
832 839 .checkboxes {
833 840 float: left;
834 841 width: @checkboxes-width;
835 842 margin-bottom: @padding;
836 843
837 844 .checkbox {
838 845 width: 100%;
839 846
840 847 label {
841 848 margin: 0;
842 849 padding: 0;
843 850 }
844 851 }
845 852
846 853 .checkbox + .checkbox {
847 854 display: inline-block;
848 855 }
849 856
850 857 label {
851 858 margin-right: 1em;
852 859 }
853 860 }
854 861
855 862 // CHANGELOG
856 863 .container_header {
857 864 float: left;
858 865 display: block;
859 866 width: 100%;
860 867 margin: @padding 0 @padding;
861 868
862 869 #filter_changelog {
863 870 float: left;
864 871 margin-right: @padding;
865 872 }
866 873
867 874 .breadcrumbs_light {
868 875 display: inline-block;
869 876 }
870 877 }
871 878
872 879 .info_box {
873 880 float: right;
874 881 }
875 882
876 883
877 884 #graph_nodes {
878 885 padding-top: 43px;
879 886 }
880 887
881 888 #graph_content{
882 889
883 890 // adjust for table headers so that graph renders properly
884 891 // #graph_nodes padding - table cell padding
885 892 padding-top: (@space - (@basefontsize * 2.4));
886 893
887 894 &.graph_full_width {
888 895 width: 100%;
889 896 max-width: 100%;
890 897 }
891 898 }
892 899
893 900 #graph {
894 901 .flag_status {
895 902 margin: 0;
896 903 }
897 904
898 905 .pagination-left {
899 906 float: left;
900 907 clear: both;
901 908 }
902 909
903 910 .log-container {
904 911 max-width: 345px;
905 912
906 913 .message{
907 914 max-width: 340px;
908 915 }
909 916 }
910 917
911 918 .graph-col-wrapper {
912 919 padding-left: 110px;
913 920
914 921 #graph_nodes {
915 922 width: 100px;
916 923 margin-left: -110px;
917 924 float: left;
918 925 clear: left;
919 926 }
920 927 }
921 928 }
922 929
923 930 #filter_changelog {
924 931 float: left;
925 932 }
926 933
927 934
928 935 //--- THEME ------------------//
929 936
930 937 #logo {
931 938 float: left;
932 939 margin: 9px 0 0 0;
933 940
934 941 .header {
935 942 background-color: transparent;
936 943 }
937 944
938 945 a {
939 946 display: inline-block;
940 947 }
941 948
942 949 img {
943 950 height:30px;
944 951 }
945 952 }
946 953
947 954 .logo-wrapper {
948 955 float:left;
949 956 }
950 957
951 958 .branding{
952 959 float: left;
953 960 padding: 9px 2px;
954 961 line-height: 1em;
955 962 font-size: @navigation-fontsize;
956 963 }
957 964
958 965 img {
959 966 border: none;
960 967 outline: none;
961 968 }
962 969 user-profile-header
963 970 label {
964 971
965 972 input[type="checkbox"] {
966 973 margin-right: 1em;
967 974 }
968 975 input[type="radio"] {
969 976 margin-right: 1em;
970 977 }
971 978 }
972 979
973 980 .flag_status {
974 981 margin: 2px 8px 6px 2px;
975 982 &.under_review {
976 983 .circle(5px, @alert3);
977 984 }
978 985 &.approved {
979 986 .circle(5px, @alert1);
980 987 }
981 988 &.rejected,
982 989 &.forced_closed{
983 990 .circle(5px, @alert2);
984 991 }
985 992 &.not_reviewed {
986 993 .circle(5px, @grey5);
987 994 }
988 995 }
989 996
990 997 .flag_status_comment_box {
991 998 margin: 5px 6px 0px 2px;
992 999 }
993 1000 .test_pattern_preview {
994 1001 margin: @space 0;
995 1002
996 1003 p {
997 1004 margin-bottom: 0;
998 1005 border-bottom: @border-thickness solid @border-default-color;
999 1006 color: @grey3;
1000 1007 }
1001 1008
1002 1009 .btn {
1003 1010 margin-bottom: @padding;
1004 1011 }
1005 1012 }
1006 1013 #test_pattern_result {
1007 1014 display: none;
1008 1015 &:extend(pre);
1009 1016 padding: .9em;
1010 1017 color: @grey3;
1011 1018 background-color: @grey7;
1012 1019 border-right: @border-thickness solid @border-default-color;
1013 1020 border-bottom: @border-thickness solid @border-default-color;
1014 1021 border-left: @border-thickness solid @border-default-color;
1015 1022 }
1016 1023
1017 1024 #repo_vcs_settings {
1018 1025 #inherit_overlay_vcs_default {
1019 1026 display: none;
1020 1027 }
1021 1028 #inherit_overlay_vcs_custom {
1022 1029 display: custom;
1023 1030 }
1024 1031 &.inherited {
1025 1032 #inherit_overlay_vcs_default {
1026 1033 display: block;
1027 1034 }
1028 1035 #inherit_overlay_vcs_custom {
1029 1036 display: none;
1030 1037 }
1031 1038 }
1032 1039 }
1033 1040
1034 1041 .issue-tracker-link {
1035 1042 color: @rcblue;
1036 1043 }
1037 1044
1038 1045 // Issue Tracker Table Show/Hide
1039 1046 #repo_issue_tracker {
1040 1047 #inherit_overlay {
1041 1048 display: none;
1042 1049 }
1043 1050 #custom_overlay {
1044 1051 display: custom;
1045 1052 }
1046 1053 &.inherited {
1047 1054 #inherit_overlay {
1048 1055 display: block;
1049 1056 }
1050 1057 #custom_overlay {
1051 1058 display: none;
1052 1059 }
1053 1060 }
1054 1061 }
1055 1062 table.issuetracker {
1056 1063 &.readonly {
1057 1064 tr, td {
1058 1065 color: @grey3;
1059 1066 }
1060 1067 }
1061 1068 .edit {
1062 1069 display: none;
1063 1070 }
1064 1071 .editopen {
1065 1072 .edit {
1066 1073 display: inline;
1067 1074 }
1068 1075 .entry {
1069 1076 display: none;
1070 1077 }
1071 1078 }
1072 1079 tr td.td-action {
1073 1080 min-width: 117px;
1074 1081 }
1075 1082 td input {
1076 1083 max-width: none;
1077 1084 min-width: 30px;
1078 1085 width: 80%;
1079 1086 }
1080 1087 .issuetracker_pref input {
1081 1088 width: 40%;
1082 1089 }
1083 1090 input.edit_issuetracker_update {
1084 1091 margin-right: 0;
1085 1092 width: auto;
1086 1093 }
1087 1094 }
1088 1095
1089 1096
1090 1097 //Permissions Settings
1091 1098 #add_perm {
1092 1099 margin: 0 0 @padding;
1093 1100 cursor: pointer;
1094 1101 }
1095 1102
1096 1103 .perm_ac {
1097 1104 input {
1098 1105 width: 95%;
1099 1106 }
1100 1107 }
1101 1108
1102 1109 .autocomplete-suggestions {
1103 1110 width: auto !important; // overrides autocomplete.js
1104 1111 margin: 0;
1105 1112 border: @border-thickness solid @rcblue;
1106 1113 border-radius: @border-radius;
1107 1114 color: @rcblue;
1108 1115 background-color: white;
1109 1116 }
1110 1117 .autocomplete-selected {
1111 1118 background: #F0F0F0;
1112 1119 }
1113 1120 .ac-container-wrap {
1114 1121 margin: 0;
1115 1122 padding: 8px;
1116 1123 border-bottom: @border-thickness solid @rclightblue;
1117 1124 list-style-type: none;
1118 1125 cursor: pointer;
1119 1126
1120 1127 &:hover {
1121 1128 background-color: @rclightblue;
1122 1129 }
1123 1130
1124 1131 img {
1125 1132 margin-right: 1em;
1126 1133 }
1127 1134
1128 1135 strong {
1129 1136 font-weight: normal;
1130 1137 }
1131 1138 }
1132 1139
1133 1140 // Settings Dropdown
1134 1141 .user-menu .container {
1135 1142 padding: 0 4px;
1136 1143 margin: 0;
1137 1144 }
1138 1145
1139 1146 .user-menu .gravatar {
1140 1147 cursor: pointer;
1141 1148 }
1142 1149
1143 1150 .codeblock {
1144 1151 margin-bottom: @padding;
1145 1152 clear: both;
1146 1153
1147 1154 .stats{
1148 1155 overflow: hidden;
1149 1156 }
1150 1157
1151 1158 .message{
1152 1159 textarea{
1153 1160 margin: 0;
1154 1161 }
1155 1162 }
1156 1163
1157 1164 .code-header {
1158 1165 .stats {
1159 1166 line-height: 2em;
1160 1167
1161 1168 .revision_id {
1162 1169 margin-left: 0;
1163 1170 }
1164 1171 .buttons {
1165 1172 padding-right: 0;
1166 1173 }
1167 1174 }
1168 1175
1169 1176 .item{
1170 1177 margin-right: 0.5em;
1171 1178 }
1172 1179 }
1173 1180
1174 1181 #editor_container{
1175 1182 position: relative;
1176 1183 margin: @padding;
1177 1184 }
1178 1185 }
1179 1186
1180 1187 #file_history_container {
1181 1188 display: none;
1182 1189 }
1183 1190
1184 1191 .file-history-inner {
1185 1192 margin-bottom: 10px;
1186 1193 }
1187 1194
1188 1195 // Pull Requests
1189 1196 .summary-details {
1190 1197 width: 72%;
1191 1198 }
1192 1199 .pr-summary {
1193 1200 border-bottom: @border-thickness solid @grey5;
1194 1201 margin-bottom: @space;
1195 1202 }
1196 1203 .reviewers-title {
1197 1204 width: 25%;
1198 1205 min-width: 200px;
1199 1206 }
1200 1207 .reviewers {
1201 1208 width: 25%;
1202 1209 min-width: 200px;
1203 1210 }
1204 1211 .reviewers ul li {
1205 1212 position: relative;
1206 1213 width: 100%;
1207 1214 margin-bottom: 8px;
1208 1215 }
1209 1216 .reviewers_member {
1210 1217 width: 100%;
1211 1218 overflow: auto;
1212 1219 }
1213 1220 .reviewer_status {
1214 1221 display: inline-block;
1215 1222 vertical-align: top;
1216 1223 width: 7%;
1217 1224 min-width: 20px;
1218 1225 height: 1.2em;
1219 1226 margin-top: 3px;
1220 1227 line-height: 1em;
1221 1228 }
1222 1229
1223 1230 .reviewer_name {
1224 1231 display: inline-block;
1225 1232 max-width: 83%;
1226 1233 padding-right: 20px;
1227 1234 vertical-align: middle;
1228 1235 line-height: 1;
1229 1236
1230 1237 .rc-user {
1231 1238 min-width: 0;
1232 1239 margin: -2px 1em 0 0;
1233 1240 }
1234 1241
1235 1242 .reviewer {
1236 1243 float: left;
1237 1244 }
1238 1245
1239 1246 &.to-delete {
1240 1247 .user,
1241 1248 .reviewer {
1242 1249 text-decoration: line-through;
1243 1250 }
1244 1251 }
1245 1252 }
1246 1253
1247 1254 .reviewer_member_remove {
1248 1255 position: absolute;
1249 1256 right: 0;
1250 1257 top: 0;
1251 1258 width: 16px;
1252 1259 margin-bottom: 10px;
1253 1260 padding: 0;
1254 1261 color: black;
1255 1262 }
1256 1263 .reviewer_member_status {
1257 1264 margin-top: 5px;
1258 1265 }
1259 1266 .pr-summary #summary{
1260 1267 width: 100%;
1261 1268 }
1262 1269 .pr-summary .action_button:hover {
1263 1270 border: 0;
1264 1271 cursor: pointer;
1265 1272 }
1266 1273 .pr-details-title {
1267 1274 padding-bottom: 8px;
1268 1275 border-bottom: @border-thickness solid @grey5;
1269 1276 .action_button {
1270 1277 color: @rcblue;
1271 1278 }
1272 1279 }
1273 1280 .pr-details-content {
1274 1281 margin-top: @textmargin;
1275 1282 margin-bottom: @textmargin;
1276 1283 }
1277 1284 .pr-description {
1278 1285 white-space:pre-wrap;
1279 1286 }
1280 1287 .group_members {
1281 1288 margin-top: 0;
1282 1289 padding: 0;
1283 1290 list-style: outside none none;
1284 1291 }
1285 1292 .reviewer_ac .ac-input {
1286 1293 width: 92%;
1287 1294 margin-bottom: 1em;
1288 1295 }
1289 1296 #update_commits {
1290 1297 float: right;
1291 1298 }
1292 1299 .compare_view_commits tr{
1293 1300 height: 20px;
1294 1301 }
1295 1302 .compare_view_commits td {
1296 1303 vertical-align: top;
1297 1304 padding-top: 10px;
1298 1305 }
1299 1306 .compare_view_commits .author {
1300 1307 margin-left: 5px;
1301 1308 }
1302 1309
1303 1310 .compare_view_files {
1304 1311 width: 100%;
1305 1312
1306 1313 td {
1307 1314 vertical-align: middle;
1308 1315 }
1309 1316 }
1310 1317
1311 1318 .compare_view_filepath {
1312 1319 color: @grey1;
1313 1320 }
1314 1321
1315 1322 .show_more {
1316 1323 display: inline-block;
1317 1324 position: relative;
1318 1325 vertical-align: middle;
1319 1326 width: 4px;
1320 1327 height: @basefontsize;
1321 1328
1322 1329 &:after {
1323 1330 content: "\00A0\25BE";
1324 1331 display: inline-block;
1325 1332 width:10px;
1326 1333 line-height: 5px;
1327 1334 font-size: 12px;
1328 1335 cursor: pointer;
1329 1336 }
1330 1337 }
1331 1338
1332 1339 .journal_more .show_more {
1333 1340 display: inline;
1334 1341
1335 1342 &:after {
1336 1343 content: none;
1337 1344 }
1338 1345 }
1339 1346
1340 1347 .open .show_more:after,
1341 1348 .select2-dropdown-open .show_more:after {
1342 1349 .rotate(180deg);
1343 1350 margin-left: 4px;
1344 1351 }
1345 1352
1346 1353
1347 1354 .compare_view_commits .collapse_commit:after {
1348 1355 cursor: pointer;
1349 1356 content: "\00A0\25B4";
1350 1357 margin-left: -3px;
1351 1358 font-size: 17px;
1352 1359 color: @grey4;
1353 1360 }
1354 1361
1355 1362 .diff_links {
1356 1363 margin-left: 8px;
1357 1364 }
1358 1365
1359 1366 p.ancestor {
1360 1367 margin: @padding 0;
1361 1368 }
1362 1369
1363 1370 .cs_icon_td input[type="checkbox"] {
1364 1371 display: none;
1365 1372 }
1366 1373
1367 1374 .cs_icon_td .expand_file_icon:after {
1368 1375 cursor: pointer;
1369 1376 content: "\00A0\25B6";
1370 1377 font-size: 12px;
1371 1378 color: @grey4;
1372 1379 }
1373 1380
1374 1381 .cs_icon_td .collapse_file_icon:after {
1375 1382 cursor: pointer;
1376 1383 content: "\00A0\25BC";
1377 1384 font-size: 12px;
1378 1385 color: @grey4;
1379 1386 }
1380 1387
1381 1388 /*new binary
1382 1389 NEW_FILENODE = 1
1383 1390 DEL_FILENODE = 2
1384 1391 MOD_FILENODE = 3
1385 1392 RENAMED_FILENODE = 4
1386 1393 COPIED_FILENODE = 5
1387 1394 CHMOD_FILENODE = 6
1388 1395 BIN_FILENODE = 7
1389 1396 */
1390 1397 .cs_files_expand {
1391 1398 font-size: @basefontsize + 5px;
1392 1399 line-height: 1.8em;
1393 1400 float: right;
1394 1401 }
1395 1402
1396 1403 .cs_files_expand span{
1397 1404 color: @rcblue;
1398 1405 cursor: pointer;
1399 1406 }
1400 1407 .cs_files {
1401 1408 clear: both;
1402 1409 padding-bottom: @padding;
1403 1410
1404 1411 .cur_cs {
1405 1412 margin: 10px 2px;
1406 1413 font-weight: bold;
1407 1414 }
1408 1415
1409 1416 .node {
1410 1417 float: left;
1411 1418 }
1412 1419
1413 1420 .changes {
1414 1421 float: right;
1415 1422 color: white;
1416 1423 font-size: @basefontsize - 4px;
1417 1424 margin-top: 4px;
1418 1425 opacity: 0.6;
1419 1426 filter: Alpha(opacity=60); /* IE8 and earlier */
1420 1427
1421 1428 .added {
1422 1429 background-color: @alert1;
1423 1430 float: left;
1424 1431 text-align: center;
1425 1432 }
1426 1433
1427 1434 .deleted {
1428 1435 background-color: @alert2;
1429 1436 float: left;
1430 1437 text-align: center;
1431 1438 }
1432 1439
1433 1440 .bin {
1434 1441 background-color: @alert1;
1435 1442 text-align: center;
1436 1443 }
1437 1444
1438 1445 /*new binary*/
1439 1446 .bin.bin1 {
1440 1447 background-color: @alert1;
1441 1448 text-align: center;
1442 1449 }
1443 1450
1444 1451 /*deleted binary*/
1445 1452 .bin.bin2 {
1446 1453 background-color: @alert2;
1447 1454 text-align: center;
1448 1455 }
1449 1456
1450 1457 /*mod binary*/
1451 1458 .bin.bin3 {
1452 1459 background-color: @grey2;
1453 1460 text-align: center;
1454 1461 }
1455 1462
1456 1463 /*rename file*/
1457 1464 .bin.bin4 {
1458 1465 background-color: @alert4;
1459 1466 text-align: center;
1460 1467 }
1461 1468
1462 1469 /*copied file*/
1463 1470 .bin.bin5 {
1464 1471 background-color: @alert4;
1465 1472 text-align: center;
1466 1473 }
1467 1474
1468 1475 /*chmod file*/
1469 1476 .bin.bin6 {
1470 1477 background-color: @grey2;
1471 1478 text-align: center;
1472 1479 }
1473 1480 }
1474 1481 }
1475 1482
1476 1483 .cs_files .cs_added, .cs_files .cs_A,
1477 1484 .cs_files .cs_added, .cs_files .cs_M,
1478 1485 .cs_files .cs_added, .cs_files .cs_D {
1479 1486 height: 16px;
1480 1487 padding-right: 10px;
1481 1488 margin-top: 7px;
1482 1489 text-align: left;
1483 1490 }
1484 1491
1485 1492 .cs_icon_td {
1486 1493 min-width: 16px;
1487 1494 width: 16px;
1488 1495 }
1489 1496
1490 1497 .pull-request-merge {
1491 1498 padding: 10px 0;
1492 1499 margin-top: 10px;
1493 1500 margin-bottom: 20px;
1494 1501 }
1495 1502
1496 1503 .pull-request-merge .pull-request-wrap {
1497 1504 height: 25px;
1498 1505 padding: 5px 0;
1499 1506 }
1500 1507
1501 1508 .pull-request-merge span {
1502 1509 margin-right: 10px;
1503 1510 }
1504 1511 #close_pull_request {
1505 1512 margin-right: 0px;
1506 1513 }
1507 1514
1508 1515 .empty_data {
1509 1516 color: @grey4;
1510 1517 }
1511 1518
1512 1519 #changeset_compare_view_content {
1513 1520 margin-bottom: @space;
1514 1521 clear: both;
1515 1522 width: 100%;
1516 1523 box-sizing: border-box;
1517 1524 .border-radius(@border-radius);
1518 1525
1519 1526 .help-block {
1520 1527 margin: @padding 0;
1521 1528 color: @text-color;
1522 1529 }
1523 1530
1524 1531 .empty_data {
1525 1532 margin: @padding 0;
1526 1533 }
1527 1534
1528 1535 .alert {
1529 1536 margin-bottom: @space;
1530 1537 }
1531 1538 }
1532 1539
1533 1540 .table_disp {
1534 1541 .status {
1535 1542 width: auto;
1536 1543
1537 1544 .flag_status {
1538 1545 float: left;
1539 1546 }
1540 1547 }
1541 1548 }
1542 1549
1543 1550 .status_box_menu {
1544 1551 margin: 0;
1545 1552 }
1546 1553
1547 1554 .notification-table{
1548 1555 margin-bottom: @space;
1549 1556 display: table;
1550 1557 width: 100%;
1551 1558
1552 1559 .container{
1553 1560 display: table-row;
1554 1561
1555 1562 .notification-header{
1556 1563 border-bottom: @border-thickness solid @border-default-color;
1557 1564 }
1558 1565
1559 1566 .notification-subject{
1560 1567 display: table-cell;
1561 1568 }
1562 1569 }
1563 1570 }
1564 1571
1565 1572 // Notifications
1566 1573 .notification-header{
1567 1574 display: table;
1568 1575 width: 100%;
1569 1576 padding: floor(@basefontsize/2) 0;
1570 1577 line-height: 1em;
1571 1578
1572 1579 .desc, .delete-notifications, .read-notifications{
1573 1580 display: table-cell;
1574 1581 text-align: left;
1575 1582 }
1576 1583
1577 1584 .desc{
1578 1585 width: 1163px;
1579 1586 }
1580 1587
1581 1588 .delete-notifications, .read-notifications{
1582 1589 width: 35px;
1583 1590 min-width: 35px; //fixes when only one button is displayed
1584 1591 }
1585 1592 }
1586 1593
1587 1594 .notification-body {
1588 1595 .markdown-block,
1589 1596 .rst-block {
1590 1597 padding: @padding 0;
1591 1598 }
1592 1599
1593 1600 .notification-subject {
1594 1601 padding: @textmargin 0;
1595 1602 border-bottom: @border-thickness solid @border-default-color;
1596 1603 }
1597 1604 }
1598 1605
1599 1606
1600 1607 .notifications_buttons{
1601 1608 float: right;
1602 1609 }
1603 1610
1604 1611 // Repositories
1605 1612
1606 1613 #summary.fields{
1607 1614 display: table;
1608 1615
1609 1616 .field{
1610 1617 display: table-row;
1611 1618
1612 1619 .label-summary{
1613 1620 display: table-cell;
1614 1621 min-width: @label-summary-minwidth;
1615 1622 padding-top: @padding/2;
1616 1623 padding-bottom: @padding/2;
1617 1624 padding-right: @padding/2;
1618 1625 }
1619 1626
1620 1627 .input{
1621 1628 display: table-cell;
1622 1629 padding: @padding/2;
1623 1630
1624 1631 input{
1625 1632 min-width: 29em;
1626 1633 padding: @padding/4;
1627 1634 }
1628 1635 }
1629 1636 .statistics, .downloads{
1630 1637 .disabled{
1631 1638 color: @grey4;
1632 1639 }
1633 1640 }
1634 1641 }
1635 1642 }
1636 1643
1637 1644 #summary{
1638 1645 width: 70%;
1639 1646 }
1640 1647
1641 1648
1642 1649 // Journal
1643 1650 .journal.title {
1644 1651 h5 {
1645 1652 float: left;
1646 1653 margin: 0;
1647 1654 width: 70%;
1648 1655 }
1649 1656
1650 1657 ul {
1651 1658 float: right;
1652 1659 display: inline-block;
1653 1660 margin: 0;
1654 1661 width: 30%;
1655 1662 text-align: right;
1656 1663
1657 1664 li {
1658 1665 display: inline;
1659 1666 font-size: @journal-fontsize;
1660 1667 line-height: 1em;
1661 1668
1662 1669 &:before { content: none; }
1663 1670 }
1664 1671 }
1665 1672 }
1666 1673
1667 1674 .filterexample {
1668 1675 position: absolute;
1669 1676 top: 95px;
1670 1677 left: @contentpadding;
1671 1678 color: @rcblue;
1672 1679 font-size: 11px;
1673 1680 font-family: @text-regular;
1674 1681 cursor: help;
1675 1682
1676 1683 &:hover {
1677 1684 color: @rcdarkblue;
1678 1685 }
1679 1686
1680 1687 @media (max-width:768px) {
1681 1688 position: relative;
1682 1689 top: auto;
1683 1690 left: auto;
1684 1691 display: block;
1685 1692 }
1686 1693 }
1687 1694
1688 1695
1689 1696 #journal{
1690 1697 margin-bottom: @space;
1691 1698
1692 1699 .journal_day{
1693 1700 margin-bottom: @textmargin/2;
1694 1701 padding-bottom: @textmargin/2;
1695 1702 font-size: @journal-fontsize;
1696 1703 border-bottom: @border-thickness solid @border-default-color;
1697 1704 }
1698 1705
1699 1706 .journal_container{
1700 1707 margin-bottom: @space;
1701 1708
1702 1709 .journal_user{
1703 1710 display: inline-block;
1704 1711 }
1705 1712 .journal_action_container{
1706 1713 display: block;
1707 1714 margin-top: @textmargin;
1708 1715
1709 1716 div{
1710 1717 display: inline;
1711 1718 }
1712 1719
1713 1720 div.journal_action_params{
1714 1721 display: block;
1715 1722 }
1716 1723
1717 1724 div.journal_repo:after{
1718 1725 content: "\A";
1719 1726 white-space: pre;
1720 1727 }
1721 1728
1722 1729 div.date{
1723 1730 display: block;
1724 1731 margin-bottom: @textmargin;
1725 1732 }
1726 1733 }
1727 1734 }
1728 1735 }
1729 1736
1730 1737 // Files
1731 1738 .edit-file-title {
1732 1739 border-bottom: @border-thickness solid @border-default-color;
1733 1740
1734 1741 .breadcrumbs {
1735 1742 margin-bottom: 0;
1736 1743 }
1737 1744 }
1738 1745
1739 1746 .edit-file-fieldset {
1740 1747 margin-top: @sidebarpadding;
1741 1748
1742 1749 .fieldset {
1743 1750 .left-label {
1744 1751 width: 13%;
1745 1752 }
1746 1753 .right-content {
1747 1754 width: 87%;
1748 1755 max-width: 100%;
1749 1756 }
1750 1757 .filename-label {
1751 1758 margin-top: 13px;
1752 1759 }
1753 1760 .commit-message-label {
1754 1761 margin-top: 4px;
1755 1762 }
1756 1763 .file-upload-input {
1757 1764 input {
1758 1765 display: none;
1759 1766 }
1760 1767 }
1761 1768 p {
1762 1769 margin-top: 5px;
1763 1770 }
1764 1771
1765 1772 }
1766 1773 .custom-path-link {
1767 1774 margin-left: 5px;
1768 1775 }
1769 1776 #commit {
1770 1777 resize: vertical;
1771 1778 }
1772 1779 }
1773 1780
1774 1781 .delete-file-preview {
1775 1782 max-height: 250px;
1776 1783 }
1777 1784
1778 1785 .new-file,
1779 1786 #filter_activate,
1780 1787 #filter_deactivate {
1781 1788 float: left;
1782 1789 margin: 0 0 0 15px;
1783 1790 }
1784 1791
1785 1792 h3.files_location{
1786 1793 line-height: 2.4em;
1787 1794 }
1788 1795
1789 1796 .browser-nav {
1790 1797 display: table;
1791 1798 margin-bottom: @space;
1792 1799
1793 1800
1794 1801 .info_box {
1795 1802 display: inline-table;
1796 1803 height: 2.5em;
1797 1804
1798 1805 .browser-cur-rev, .info_box_elem {
1799 1806 display: table-cell;
1800 1807 vertical-align: middle;
1801 1808 }
1802 1809
1803 1810 .info_box_elem {
1804 1811 border-top: @border-thickness solid @rcblue;
1805 1812 border-bottom: @border-thickness solid @rcblue;
1806 1813
1807 1814 #at_rev, a {
1808 1815 padding: 0.6em 0.9em;
1809 1816 margin: 0;
1810 1817 .box-shadow(none);
1811 1818 border: 0;
1812 1819 height: 12px;
1813 1820 }
1814 1821
1815 1822 input#at_rev {
1816 1823 max-width: 50px;
1817 1824 text-align: right;
1818 1825 }
1819 1826
1820 1827 &.previous {
1821 1828 border: @border-thickness solid @rcblue;
1822 1829 .disabled {
1823 1830 color: @grey4;
1824 1831 cursor: not-allowed;
1825 1832 }
1826 1833 }
1827 1834
1828 1835 &.next {
1829 1836 border: @border-thickness solid @rcblue;
1830 1837 .disabled {
1831 1838 color: @grey4;
1832 1839 cursor: not-allowed;
1833 1840 }
1834 1841 }
1835 1842 }
1836 1843
1837 1844 .browser-cur-rev {
1838 1845
1839 1846 span{
1840 1847 margin: 0;
1841 1848 color: @rcblue;
1842 1849 height: 12px;
1843 1850 display: inline-block;
1844 1851 padding: 0.7em 1em ;
1845 1852 border: @border-thickness solid @rcblue;
1846 1853 margin-right: @padding;
1847 1854 }
1848 1855 }
1849 1856 }
1850 1857
1851 1858 .search_activate {
1852 1859 display: table-cell;
1853 1860 vertical-align: middle;
1854 1861
1855 1862 input, label{
1856 1863 margin: 0;
1857 1864 padding: 0;
1858 1865 }
1859 1866
1860 1867 input{
1861 1868 margin-left: @textmargin;
1862 1869 }
1863 1870
1864 1871 }
1865 1872 }
1866 1873
1867 .file_author{
1868 margin-bottom: @padding;
1869
1870 div{
1871 display: inline-block;
1872 margin-right: 0.5em;
1873 }
1874 }
1875
1876 1874 .browser-cur-rev{
1877 1875 margin-bottom: @textmargin;
1878 1876 }
1879 1877
1880 1878 #node_filter_box_loading{
1881 1879 .info_text;
1882 1880 }
1883 1881
1884 1882 .browser-search {
1885 1883 margin: -25px 0px 5px 0px;
1886 1884 }
1887 1885
1888 1886 .node-filter {
1889 1887 font-size: @repo-title-fontsize;
1890 1888 padding: 4px 0px 0px 0px;
1891 1889
1892 1890 .node-filter-path {
1893 1891 float: left;
1894 1892 color: @grey4;
1895 1893 }
1896 1894 .node-filter-input {
1897 1895 float: left;
1898 1896 margin: -2px 0px 0px 2px;
1899 1897 input {
1900 1898 padding: 2px;
1901 1899 border: none;
1902 1900 font-size: @repo-title-fontsize;
1903 1901 }
1904 1902 }
1905 1903 }
1906 1904
1907 1905
1908 1906 .browser-result{
1909 1907 td a{
1910 1908 margin-left: 0.5em;
1911 1909 display: inline-block;
1912 1910
1913 1911 em{
1914 1912 font-family: @text-bold;
1915 1913 }
1916 1914 }
1917 1915 }
1918 1916
1919 1917 .browser-highlight{
1920 1918 background-color: @grey5-alpha;
1921 1919 }
1922 1920
1923 1921
1924 1922 // Search
1925 1923
1926 1924 .search-form{
1927 1925 #q {
1928 1926 width: @search-form-width;
1929 1927 }
1930 1928 .fields{
1931 1929 margin: 0 0 @space;
1932 1930 }
1933 1931
1934 1932 label{
1935 1933 display: inline-block;
1936 1934 margin-right: @textmargin;
1937 1935 padding-top: 0.25em;
1938 1936 }
1939 1937
1940 1938
1941 1939 .results{
1942 1940 clear: both;
1943 1941 margin: 0 0 @padding;
1944 1942 }
1945 1943 }
1946 1944
1947 1945 div.search-feedback-items {
1948 1946 display: inline-block;
1949 1947 padding:0px 0px 0px 96px;
1950 1948 }
1951 1949
1952 1950 div.search-code-body {
1953 1951 background-color: #ffffff; padding: 5px 0 5px 10px;
1954 1952 pre {
1955 1953 .match { background-color: #faffa6;}
1956 1954 .break { display: block; width: 100%; background-color: #DDE7EF; color: #747474; }
1957 1955 }
1958 1956 }
1959 1957
1960 1958 .expand_commit.search {
1961 1959 .show_more.open {
1962 1960 height: auto;
1963 1961 max-height: none;
1964 1962 }
1965 1963 }
1966 1964
1967 1965 .search-results {
1968 1966
1969 1967 h2 {
1970 1968 margin-bottom: 0;
1971 1969 }
1972 1970 .codeblock {
1973 1971 border: none;
1974 1972 background: transparent;
1975 1973 }
1976 1974
1977 1975 .codeblock-header {
1978 1976 border: none;
1979 1977 background: transparent;
1980 1978 }
1981 1979
1982 1980 .code-body {
1983 1981 border: @border-thickness solid @border-default-color;
1984 1982 .border-radius(@border-radius);
1985 1983 }
1986 1984
1987 1985 .td-commit {
1988 1986 &:extend(pre);
1989 1987 border-bottom: @border-thickness solid @border-default-color;
1990 1988 }
1991 1989
1992 1990 .message {
1993 1991 height: auto;
1994 1992 max-width: 350px;
1995 1993 white-space: normal;
1996 1994 text-overflow: initial;
1997 1995 overflow: visible;
1998 1996
1999 1997 .match { background-color: #faffa6;}
2000 1998 .break { background-color: #DDE7EF; width: 100%; color: #747474; display: block; }
2001 1999 }
2002 2000
2003 2001 }
2004 2002
2005 2003 table.rctable td.td-search-results div {
2006 2004 max-width: 100%;
2007 2005 }
2008 2006
2009 2007 #tip-box, .tip-box{
2010 2008 padding: @menupadding/2;
2011 2009 display: block;
2012 2010 border: @border-thickness solid @border-highlight-color;
2013 2011 .border-radius(@border-radius);
2014 2012 background-color: white;
2015 2013 z-index: 99;
2016 2014 white-space: pre-wrap;
2017 2015 }
2018 2016
2019 2017 #linktt {
2020 2018 width: 79px;
2021 2019 }
2022 2020
2023 2021 #help_kb .modal-content{
2024 2022 max-width: 750px;
2025 2023 margin: 10% auto;
2026 2024
2027 2025 table{
2028 2026 td,th{
2029 2027 border-bottom: none;
2030 2028 line-height: 2.5em;
2031 2029 }
2032 2030 th{
2033 2031 padding-bottom: @textmargin/2;
2034 2032 }
2035 2033 td.keys{
2036 2034 text-align: center;
2037 2035 }
2038 2036 }
2039 2037
2040 2038 .block-left{
2041 2039 width: 45%;
2042 2040 margin-right: 5%;
2043 2041 }
2044 2042 .modal-footer{
2045 2043 clear: both;
2046 2044 }
2047 2045 .key.tag{
2048 2046 padding: 0.5em;
2049 2047 background-color: @rcblue;
2050 2048 color: white;
2051 2049 border-color: @rcblue;
2052 2050 .box-shadow(none);
2053 2051 }
2054 2052 }
2055 2053
2056 2054
2057 2055
2058 2056 //--- IMPORTS FOR REFACTORED STYLES ------------------//
2059 2057
2060 2058 @import 'statistics-graph';
2061 2059 @import 'tables';
2062 2060 @import 'forms';
2063 2061 @import 'diff';
2064 2062 @import 'summary';
2065 2063 @import 'navigation';
2066 2064
2067 2065 //--- SHOW/HIDE SECTIONS --//
2068 2066
2069 2067 .btn-collapse {
2070 2068 float: right;
2071 2069 text-align: right;
2072 2070 font-family: @text-light;
2073 2071 font-size: @basefontsize;
2074 2072 cursor: pointer;
2075 2073 border: none;
2076 2074 color: @rcblue;
2077 2075 }
2078 2076
2079 2077 table.rctable,
2080 2078 table.dataTable {
2081 2079 .btn-collapse {
2082 2080 float: right;
2083 2081 text-align: right;
2084 2082 }
2085 2083 }
2086 2084
2087 2085
2088 2086 // TODO: johbo: Fix for IE10, this avoids that we see a border
2089 2087 // and padding around checkboxes and radio boxes. Move to the right place,
2090 2088 // or better: Remove this once we did the form refactoring.
2091 2089 input[type=checkbox],
2092 2090 input[type=radio] {
2093 2091 padding: 0;
2094 2092 border: none;
2095 2093 }
@@ -1,256 +1,260 b''
1 1 // summary.less
2 2 // For use in RhodeCode applications;
3 3 // Used for headers and file detail summary screens.
4 4
5 5 .summary {
6 6 float: left;
7 7 position: relative;
8 8 width: 100%;
9 9 margin: 0;
10 10 padding: 0;
11 11
12 12 .summary-detail-header {
13 13 float: left;
14 14 display: block;
15 15 width: 100%;
16 16 margin-bottom: @textmargin;
17 17 padding: 0 0 .5em 0;
18 18 border-bottom: @border-thickness solid @border-default-color;
19 19
20 20 .breadcrumbs {
21 21 float: left;
22 22 display: inline;
23 23 margin: 0;
24 24 padding: 0;
25 25 }
26 26 h4 {
27 27 float: left;
28 28 margin: 0 1em 0 0;
29 29 padding: 0;
30 30 line-height: 1.2em;
31 31 font-size: @basefontsize;
32 32 }
33 33
34 34 .action_link {
35 35 float: right;
36 36 }
37 37
38 38 .new-file {
39 39 float: right;
40 40 margin-top: -1.5em;
41 41 }
42 42 }
43 43
44 44 .summary-detail {
45 45 float: left;
46 46 position: relative;
47 47 width: 73%;
48 48 margin: 0 3% @space 0;
49 49 padding: 0;
50 50
51 51 .file_diff_buttons {
52 52 margin-top: @space;
53 53 }
54 54
55 55 // commit message
56 56 .commit {
57 57 white-space: pre-wrap;
58 58 }
59 59
60 60 #clone_url,
61 61 #clone_url_id {
62 62 min-width: 29em;
63 63 padding: @padding/4;
64 64 }
65 65
66 66 &.directory {
67 67 margin-bottom: 0;
68 68 }
69 69
70 70 .desc {
71 71 white-space: pre-wrap;
72 72 }
73 73 .disabled {
74 74 opacity: .5;
75 75 }
76 76 .help-block {
77 77 color: inherit;
78 78 margin: 0;
79 79 }
80 80 }
81 81
82 82 .sidebar-right {
83 83 float: left;
84 84 width: 24%;
85 85 margin: 0;
86 86 padding: 0;
87 87
88 88 ul {
89 89 margin-left: 0;
90 90 padding-left: 0;
91 91
92 92 li {
93 93
94 94 &:before {
95 95 content: none;
96 96 width: 0;
97 97 }
98 98 }
99 99 }
100 100 }
101 101
102 102 #clone_by_name, #clone_by_id{
103 103 display: inline-block;
104 104 margin-left: @padding;
105 105 }
106 106
107 107 .codeblock {
108 108 border: none;
109 109 background-color: transparent;
110 110 }
111 111
112 112 .code-body {
113 113 border: @border-thickness solid @border-default-color;
114 114 .border-radius(@border-radius);
115 115 }
116 116 }
117 117
118 118 // this is used outside of just the summary
119 119 .fieldset, // similar to form fieldset
120 120 .summary .sidebar-right-content { // these have to match
121 121 clear: both;
122 122 float: left;
123 123 position: relative;
124 124 display:block;
125 125 width: 100%;
126 126 min-height: 1em;
127 127 margin-bottom: @textmargin;
128 128 padding: 0;
129 129 line-height: 1.2em;
130 130
131 131 &:after { // clearfix
132 132 content: "";
133 133 clear: both;
134 134 width: 100%;
135 135 height: 1em;
136 136 }
137 137 }
138 138
139 139 .summary .sidebar-right-content {
140 140 margin-bottom: @space;
141
142 .rc-user {
143 min-width: 0;
144 }
141 145 }
142 146
143 147 .fieldset {
144 148
145 149 .left-label { // similar to form legend
146 150 float: left;
147 151 display: block;
148 152 width: 25%;
149 153 margin: 0;
150 154 padding: 0;
151 155 font-family: @text-semibold;
152 156 }
153 157
154 158 .right-content { // similar to form fields
155 159 float: left;
156 160 display: block;
157 161 width: 75%;
158 162 margin: 0 0 0 -15%;
159 163 padding: 0 0 0 15%;
160 164
161 165 .truncate-wrap,
162 166 .truncate {
163 167 max-width: 100%;
164 168 width: 100%;
165 169 }
166 170
167 171 .commit-long {
168 172 overflow-x: auto;
169 173 }
170 174 }
171 175 }
172 176
173 177 // expand commit message
174 178 #message_expand {
175 179 clear: both;
176 180 display: block;
177 181 color: @rcblue;
178 182 cursor: pointer;
179 183 }
180 184
181 185 #trimmed_message_box {
182 186 max-height: floor(2 * @basefontsize * 1.2); // 2 lines * line-height
183 187 overflow: hidden;
184 188 }
185 189
186 190 // show/hide comments button
187 191 .show-inline-comments {
188 192 display: inline;
189 193 cursor: pointer;
190 194
191 195 .comments-show { display: inline; }
192 196 .comments-hide { display: none; }
193 197
194 198 &.comments-visible {
195 199 .comments-show { display: none; }
196 200 .comments-hide { display: inline; }
197 201 }
198 202 }
199 203
200 204 // Quick Start section
201 205 .quick_start {
202 206 float: left;
203 207 display: block;
204 208 position: relative;
205 209
206 210 // adds some space to make copy and paste easier
207 211 .left-label,
208 212 .right-content {
209 213 line-height: 1.6em;
210 214 }
211 215 }
212 216
213 217 .submodule {
214 218 .summary-detail {
215 219 width: 100%;
216 220
217 221 .btn-collapse {
218 222 display: none;
219 223 }
220 224 }
221 225 }
222 226
223 227 .codeblock-header {
224 228 float: left;
225 229 display: block;
226 230 width: 100%;
227 231 margin: 0;
228 232 padding: @space 0 @padding 0;
229 233 border-top: @border-thickness solid @border-default-color;
230 234
231 235 .stats {
232 236 float: left;
233 237 width: 50%;
234 238 }
235 239
236 240 .buttons {
237 241 float: right;
238 242 width: 50%;
239 243 text-align: right;
240 244 color: @grey4;
241 245 }
242 246 }
243 247
244 248 #summary-menu-stats {
245 249
246 250 .stats-bullet {
247 251 color: @grey3;
248 252 min-width: 3em;
249 253 }
250 254
251 255 .repo-size {
252 256 margin-bottom: .5em;
253 257 }
254 258
255 259 }
256 260
@@ -1,58 +1,60 b''
1 1 ## -*- coding: utf-8 -*-
2 <%namespace name="base" file="/base/base.html"/>
3
2 4 %if c.users_log:
3 5 <table class="rctable admin_log">
4 6 <tr>
5 7 <th>${_('Username')}</th>
6 8 <th>${_('Action')}</th>
7 9 <th>${_('Repository')}</th>
8 10 <th>${_('Date')}</th>
9 11 <th>${_('From IP')}</th>
10 12 </tr>
11 13
12 14 %for cnt,l in enumerate(c.users_log):
13 15 <tr class="parity${cnt%2}">
14 16 <td class="td-user">
15 %if l.user is not None:
16 ${h.link_to(l.user.username,h.url('edit_user', user_id=l.user.user_id))}
17 %else:
18 ${l.username}
19 %endif
17 %if l.user is not None:
18 ${base.gravatar_with_user(l.user.email)}
19 %else:
20 ${l.username}
21 %endif
20 22 </td>
21 23 <td class="td-journalaction">${h.action_parser(l)[0]()}
22 24 <div class="journal_action_params">
23 25 ${h.literal(h.action_parser(l)[1]())}
24 26 </div>
25 27 </td>
26 28 <td class="td-componentname">
27 29 %if l.repository is not None:
28 30 ${h.link_to(l.repository.repo_name,h.url('summary_home',repo_name=l.repository.repo_name))}
29 31 %else:
30 32 ${l.repository_name}
31 33 %endif
32 34 </td>
33 35
34 36 <td class="td-time">${h.format_date(l.action_date)}</td>
35 37 <td class="td-ip">${l.user_ip}</td>
36 38 </tr>
37 39 %endfor
38 40 </table>
39 41
40 42 <div class="pagination-wh pagination-left">
41 43 ${c.users_log.pager('$link_previous ~2~ $link_next')}
42 44 </div>
43 45 %else:
44 46 ${_('No actions yet')}
45 47 %endif
46 48
47 49 <script type="text/javascript">
48 50 $(function(){
49 51 //because this is loaded on every pjax request, it must run only once
50 52 //therefore the .one method
51 53 $(document).on('pjax:complete',function(){
52 54 show_more_event();
53 55 });
54 56
55 57 $(document).pjax('#user_log .pager_link', '#user_log');
56 58 });
57 59 </script>
58 60
@@ -1,655 +1,656 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%inherit file="root.html"/>
3 3
4 4 <div class="outerwrapper">
5 5 <!-- HEADER -->
6 6 <div class="header">
7 7 <div id="header-inner" class="wrapper">
8 8 <div id="logo">
9 9 <div class="logo-wrapper">
10 10 <a href="${h.url('home')}"><img src="${h.url('/images/rhodecode-logo-white-216x60.png')}" alt="RhodeCode"/></a>
11 11 </div>
12 12 %if c.rhodecode_name:
13 13 <div class="branding">- ${h.branding(c.rhodecode_name)}</div>
14 14 %endif
15 15 </div>
16 16 <!-- MENU BAR NAV -->
17 17 ${self.menu_bar_nav()}
18 18 <!-- END MENU BAR NAV -->
19 19 ${self.body()}
20 20 </div>
21 21 </div>
22 22 ${self.menu_bar_subnav()}
23 23 <!-- END HEADER -->
24 24
25 25 <!-- CONTENT -->
26 26 <div id="content" class="wrapper">
27 27 ${self.flash_msg()}
28 28 <div class="main">
29 29 ${next.main()}
30 30 </div>
31 31 </div>
32 32 <!-- END CONTENT -->
33 33
34 34 </div>
35 35 <!-- FOOTER -->
36 36 <div id="footer">
37 37 <div id="footer-inner" class="title wrapper">
38 38 <div>
39 39 <p class="footer-link-right">
40 40 % if c.visual.show_version:
41 41 RhodeCode Enterprise ${c.rhodecode_version} ${c.rhodecode_edition}
42 42 % endif
43 43 &copy; 2010-${h.datetime.today().year}, <a href="${h.url('rhodecode_official')}" target="_blank">RhodeCode GmbH</a>. All rights reserved.
44 44 % if c.visual.rhodecode_support_url:
45 45 <a href="${c.visual.rhodecode_support_url}" target="_blank">${_('Support')}</a>
46 46 % endif
47 47 </p>
48 48 <% sid = 'block' if request.GET.get('showrcid') else 'none' %>
49 49 <p class="server-instance" style="display:${sid}">
50 50 ## display hidden instance ID if specially defined
51 51 % if c.rhodecode_instanceid:
52 52 ${_('RhodeCode instance id: %s') % c.rhodecode_instanceid}
53 53 % endif
54 54 </p>
55 55 </div>
56 56 </div>
57 57 </div>
58 58
59 59 <!-- END FOOTER -->
60 60
61 61 ### MAKO DEFS ###
62 62
63 63 <%def name="menu_bar_subnav()">
64 64 </%def>
65 65
66 66 <%def name="flash_msg()">
67 67 <%include file="/base/flash_msg.html"/>
68 68 </%def>
69 69
70 70 <%def name="breadcrumbs(class_='breadcrumbs')">
71 71 <div class="${class_}">
72 72 ${self.breadcrumbs_links()}
73 73 </div>
74 74 </%def>
75 75
76 76 <%def name="admin_menu()">
77 77 <ul class="admin_menu submenu">
78 78 <li><a href="${h.url('admin_home')}">${_('Admin journal')}</a></li>
79 79 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
80 80 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
81 81 <li><a href="${h.url('users')}">${_('Users')}</a></li>
82 82 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
83 83 <li><a href="${h.url('admin_permissions_application')}">${_('Permissions')}</a></li>
84 84 <li><a href="${h.route_path('auth_home', traverse='')}">${_('Authentication')}</a></li>
85 85 <li><a href="${h.url('admin_defaults_repositories')}">${_('Defaults')}</a></li>
86 86 <li class="last"><a href="${h.url('admin_settings')}">${_('Settings')}</a></li>
87 87 </ul>
88 88 </%def>
89 89
90 90
91 91 <%def name="dt_info_panel(elements)">
92 92 <dl class="dl-horizontal">
93 93 %for dt, dd, title, show_items in elements:
94 94 <dt>${dt}:</dt>
95 95 <dd title="${title}">
96 96 %if callable(dd):
97 97 ## allow lazy evaluation of elements
98 98 ${dd()}
99 99 %else:
100 100 ${dd}
101 101 %endif
102 102 %if show_items:
103 103 <span class="btn-collapse" data-toggle="item-${h.md5(dt)[:6]}-details">${_('Show More')} </span>
104 104 %endif
105 105 </dd>
106 106
107 107 %if show_items:
108 108 <div class="collapsable-content" data-toggle="item-${h.md5(dt)[:6]}-details" style="display: none">
109 109 %for item in show_items:
110 110 <dt></dt>
111 111 <dd>${item}</dd>
112 112 %endfor
113 113 </div>
114 114 %endif
115 115
116 116 %endfor
117 117 </dl>
118 118 </%def>
119 119
120 120
121 121 <%def name="gravatar(email, size=16)">
122 122 <%
123 123 if (size > 16):
124 124 gravatar_class = 'gravatar gravatar-large'
125 125 else:
126 126 gravatar_class = 'gravatar'
127 127 %>
128 128 <%doc>
129 129 TODO: johbo: For now we serve double size images to make it smooth
130 130 for retina. This is how it worked until now. Should be replaced
131 131 with a better solution at some point.
132 132 </%doc>
133 133 <img class="${gravatar_class}" src="${h.gravatar_url(email, size * 2)}" height="${size}" width="${size}">
134 134 </%def>
135 135
136 136
137 137 <%def name="gravatar_with_user(contact, size=16, show_disabled=False)">
138 <div class="rc-user tooltip" title="${contact}">
139 ${self.gravatar(h.email_or_none(contact), size)}
138 <% email = h.email_or_none(contact) %>
139 <div class="rc-user tooltip" title="${h.author_string(email)}">
140 ${self.gravatar(email, size)}
140 141 <span class="${'user user-disabled' if show_disabled else 'user'}"> ${h.link_to_user(contact)}</span>
141 142 </div>
142 143 </%def>
143 144
144 145
145 146 ## admin menu used for people that have some admin resources
146 147 <%def name="admin_menu_simple(repositories=None, repository_groups=None, user_groups=None)">
147 148 <ul class="submenu">
148 149 %if repositories:
149 150 <li><a href="${h.url('repos')}">${_('Repositories')}</a></li>
150 151 %endif
151 152 %if repository_groups:
152 153 <li><a href="${h.url('repo_groups')}">${_('Repository groups')}</a></li>
153 154 %endif
154 155 %if user_groups:
155 156 <li><a href="${h.url('users_groups')}">${_('User groups')}</a></li>
156 157 %endif
157 158 </ul>
158 159 </%def>
159 160
160 161 <%def name="repo_page_title(repo_instance)">
161 162 <div class="title-content">
162 163 <div class="title-main">
163 164 ## SVN/HG/GIT icons
164 165 %if h.is_hg(repo_instance):
165 166 <i class="icon-hg"></i>
166 167 %endif
167 168 %if h.is_git(repo_instance):
168 169 <i class="icon-git"></i>
169 170 %endif
170 171 %if h.is_svn(repo_instance):
171 172 <i class="icon-svn"></i>
172 173 %endif
173 174
174 175 ## public/private
175 176 %if repo_instance.private:
176 177 <i class="icon-repo-private"></i>
177 178 %else:
178 179 <i class="icon-repo-public"></i>
179 180 %endif
180 181
181 182 ## repo name with group name
182 183 ${h.breadcrumb_repo_link(c.rhodecode_db_repo)}
183 184
184 185 </div>
185 186
186 187 ## FORKED
187 188 %if repo_instance.fork:
188 189 <p>
189 190 <i class="icon-code-fork"></i> ${_('Fork of')}
190 191 <a href="${h.url('summary_home',repo_name=repo_instance.fork.repo_name)}">${repo_instance.fork.repo_name}</a>
191 192 </p>
192 193 %endif
193 194
194 195 ## IMPORTED FROM REMOTE
195 196 %if repo_instance.clone_uri:
196 197 <p>
197 198 <i class="icon-code-fork"></i> ${_('Clone from')}
198 199 <a href="${h.url(h.safe_str(h.hide_credentials(repo_instance.clone_uri)))}">${h.hide_credentials(repo_instance.clone_uri)}</a>
199 200 </p>
200 201 %endif
201 202
202 203 ## LOCKING STATUS
203 204 %if repo_instance.locked[0]:
204 205 <p class="locking_locked">
205 206 <i class="icon-repo-lock"></i>
206 207 ${_('Repository locked by %(user)s') % {'user': h.person_by_id(repo_instance.locked[0])}}
207 208 </p>
208 209 %elif repo_instance.enable_locking:
209 210 <p class="locking_unlocked">
210 211 <i class="icon-repo-unlock"></i>
211 212 ${_('Repository not locked. Pull repository to lock it.')}
212 213 </p>
213 214 %endif
214 215
215 216 </div>
216 217 </%def>
217 218
218 219 <%def name="repo_menu(active=None)">
219 220 <%
220 221 def is_active(selected):
221 222 if selected == active:
222 223 return "active"
223 224 %>
224 225
225 226 <!--- CONTEXT BAR -->
226 227 <div id="context-bar">
227 228 <div class="wrapper">
228 229 <ul id="context-pages" class="horizontal-list navigation">
229 230 <li class="${is_active('summary')}"><a class="menulink" href="${h.url('summary_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Summary')}</div></a></li>
230 231 <li class="${is_active('changelog')}"><a class="menulink" href="${h.url('changelog_home', repo_name=c.repo_name)}"><div class="menulabel">${_('Changelog')}</div></a></li>
231 232 <li class="${is_active('files')}"><a class="menulink" href="${h.url('files_home', repo_name=c.repo_name, revision=c.rhodecode_db_repo.landing_rev[1])}"><div class="menulabel">${_('Files')}</div></a></li>
232 233 <li class="${is_active('compare')}">
233 234 <a class="menulink" href="${h.url('compare_home',repo_name=c.repo_name)}"><div class="menulabel">${_('Compare')}</div></a>
234 235 </li>
235 236 ## TODO: anderson: ideally it would have a function on the scm_instance "enable_pullrequest() and enable_fork()"
236 237 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
237 238 <li class="${is_active('showpullrequest')}">
238 239 <a class="menulink" href="${h.url('pullrequest_show_all',repo_name=c.repo_name)}" title="${_('Show Pull Requests for %s') % c.repo_name}">
239 240 %if c.repository_pull_requests:
240 241 <span class="pr_notifications">${c.repository_pull_requests}</span>
241 242 %endif
242 243 <div class="menulabel">${_('Pull Requests')}</div>
243 244 </a>
244 245 </li>
245 246 %endif
246 247 <li class="${is_active('options')}">
247 248 <a class="menulink" href="#" class="dropdown"><div class="menulabel">${_('Options')} <div class="show_more"></div></div></a>
248 249 <ul class="submenu">
249 250 %if h.HasRepoPermissionAll('repository.admin')(c.repo_name):
250 251 <li><a href="${h.url('edit_repo',repo_name=c.repo_name)}">${_('Settings')}</a></li>
251 252 %endif
252 253 %if c.rhodecode_db_repo.fork:
253 254 <li><a href="${h.url('compare_url',repo_name=c.rhodecode_db_repo.fork.repo_name,source_ref_type=c.rhodecode_db_repo.landing_rev[0],source_ref=c.rhodecode_db_repo.landing_rev[1], target_repo=c.repo_name,target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1], merge=1)}">
254 255 ${_('Compare fork')}</a></li>
255 256 %endif
256 257
257 258 <li><a href="${h.url('search_repo_home',repo_name=c.repo_name)}">${_('Search')}</a></li>
258 259
259 260 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name) and c.rhodecode_db_repo.enable_locking:
260 261 %if c.rhodecode_db_repo.locked[0]:
261 262 <li><a class="locking_del" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Unlock')}</a></li>
262 263 %else:
263 264 <li><a class="locking_add" href="${h.url('toggle_locking',repo_name=c.repo_name)}">${_('Lock')}</a></li>
264 265 %endif
265 266 %endif
266 267 %if c.rhodecode_user.username != h.DEFAULT_USER:
267 268 %if c.rhodecode_db_repo.repo_type in ['git','hg']:
268 269 <li><a href="${h.url('repo_fork_home',repo_name=c.repo_name)}">${_('Fork')}</a></li>
269 270 <li><a href="${h.url('pullrequest_home',repo_name=c.repo_name)}">${_('Create Pull Request')}</a></li>
270 271 %endif
271 272 %endif
272 273 </ul>
273 274 </li>
274 275 </ul>
275 276 </div>
276 277 <div class="clear"></div>
277 278 </div>
278 279 <!--- END CONTEXT BAR -->
279 280
280 281 </%def>
281 282
282 283 <%def name="usermenu()">
283 284 ## USER MENU
284 285 <li id="quick_login_li">
285 286 <a id="quick_login_link" class="menulink childs">
286 287 ${gravatar(c.rhodecode_user.email, 20)}
287 288 <span class="user">
288 289 %if c.rhodecode_user.username != h.DEFAULT_USER:
289 290 <span class="menu_link_user">${c.rhodecode_user.username}</span><div class="show_more"></div>
290 291 %else:
291 292 <span>${_('Sign in')}</span>
292 293 %endif
293 294 </span>
294 295 </a>
295 296
296 297 <div class="user-menu submenu">
297 298 <div id="quick_login">
298 299 %if c.rhodecode_user.username == h.DEFAULT_USER:
299 300 <h4>${_('Sign in to your account')}</h4>
300 301 ${h.form(h.route_path('login', _query={'came_from': h.url.current()}), needs_csrf_token=False)}
301 302 <div class="form form-vertical">
302 303 <div class="fields">
303 304 <div class="field">
304 305 <div class="label">
305 306 <label for="username">${_('Username')}:</label>
306 307 </div>
307 308 <div class="input">
308 309 ${h.text('username',class_='focus',tabindex=1)}
309 310 </div>
310 311
311 312 </div>
312 313 <div class="field">
313 314 <div class="label">
314 315 <label for="password">${_('Password')}:</label>
315 316 <span class="forgot_password">${h.link_to(_('(Forgot password?)'),h.route_path('reset_password'))}</span>
316 317 </div>
317 318 <div class="input">
318 319 ${h.password('password',class_='focus',tabindex=2)}
319 320 </div>
320 321 </div>
321 322 <div class="buttons">
322 323 <div class="register">
323 324 %if h.HasPermissionAny('hg.admin', 'hg.register.auto_activate', 'hg.register.manual_activate')():
324 325 ${h.link_to(_("Don't have an account ?"),h.route_path('register'))}
325 326 %endif
326 327 </div>
327 328 <div class="submit">
328 329 ${h.submit('sign_in',_('Sign In'),class_="btn btn-small",tabindex=3)}
329 330 </div>
330 331 </div>
331 332 </div>
332 333 </div>
333 334 ${h.end_form()}
334 335 %else:
335 336 <div class="">
336 337 <div class="big_gravatar">${gravatar(c.rhodecode_user.email, 48)}</div>
337 338 <div class="full_name">${c.rhodecode_user.full_name_or_username}</div>
338 339 <div class="email">${c.rhodecode_user.email}</div>
339 340 </div>
340 341 <div class="">
341 342 <ol class="links">
342 343 <li>${h.link_to(_(u'My account'),h.url('my_account'))}</li>
343 344 <li class="logout">
344 345 ${h.secure_form(h.route_path('logout'))}
345 346 ${h.submit('log_out', _(u'Sign Out'),class_="btn btn-primary")}
346 347 ${h.end_form()}
347 348 </li>
348 349 </ol>
349 350 </div>
350 351 %endif
351 352 </div>
352 353 </div>
353 354 %if c.rhodecode_user.username != h.DEFAULT_USER:
354 355 <div class="pill_container">
355 356 % if c.unread_notifications == 0:
356 357 <a class="menu_link_notifications empty" href="${h.url('notifications')}">${c.unread_notifications}</a>
357 358 % else:
358 359 <a class="menu_link_notifications" href="${h.url('notifications')}">${c.unread_notifications}</a>
359 360 % endif
360 361 </div>
361 362 % endif
362 363 </li>
363 364 </%def>
364 365
365 366 <%def name="menu_items(active=None)">
366 367 <%
367 368 def is_active(selected):
368 369 if selected == active:
369 370 return "active"
370 371 return ""
371 372 %>
372 373 <ul id="quick" class="main_nav navigation horizontal-list">
373 374 <!-- repo switcher -->
374 375 <li class="${is_active('repositories')} repo_switcher_li has_select2">
375 376 <input id="repo_switcher" name="repo_switcher" type="hidden">
376 377 </li>
377 378
378 379 ## ROOT MENU
379 380 %if c.rhodecode_user.username != h.DEFAULT_USER:
380 381 <li class="${is_active('journal')}">
381 382 <a class="menulink" title="${_('Show activity journal')}" href="${h.url('journal')}">
382 383 <div class="menulabel">${_('Journal')}</div>
383 384 </a>
384 385 </li>
385 386 %else:
386 387 <li class="${is_active('journal')}">
387 388 <a class="menulink" title="${_('Show Public activity journal')}" href="${h.url('public_journal')}">
388 389 <div class="menulabel">${_('Public journal')}</div>
389 390 </a>
390 391 </li>
391 392 %endif
392 393 <li class="${is_active('gists')}">
393 394 <a class="menulink childs" title="${_('Show Gists')}" href="${h.url('gists')}">
394 395 <div class="menulabel">${_('Gists')}</div>
395 396 </a>
396 397 </li>
397 398 <li class="${is_active('search')}">
398 399 <a class="menulink" title="${_('Search in repositories you have access to')}" href="${h.url('search')}">
399 400 <div class="menulabel">${_('Search')}</div>
400 401 </a>
401 402 </li>
402 403 % if h.HasPermissionAll('hg.admin')('access admin main page'):
403 404 <li class="${is_active('admin')}">
404 405 <a class="menulink childs" title="${_('Admin settings')}" href="#" onclick="return false;">
405 406 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
406 407 </a>
407 408 ${admin_menu()}
408 409 </li>
409 410 % elif c.rhodecode_user.repositories_admin or c.rhodecode_user.repository_groups_admin or c.rhodecode_user.user_groups_admin:
410 411 <li class="${is_active('admin')}">
411 412 <a class="menulink childs" title="${_('Delegated Admin settings')}">
412 413 <div class="menulabel">${_('Admin')} <div class="show_more"></div></div>
413 414 </a>
414 415 ${admin_menu_simple(c.rhodecode_user.repositories_admin,
415 416 c.rhodecode_user.repository_groups_admin,
416 417 c.rhodecode_user.user_groups_admin or h.HasPermissionAny('hg.usergroup.create.true')())}
417 418 </li>
418 419 % endif
419 420 % if c.debug_style:
420 421 <li class="${is_active('debug_style')}">
421 422 <a class="menulink" title="${_('Style')}" href="${h.url('debug_style_home')}">
422 423 <div class="menulabel">${_('Style')}</div>
423 424 </a>
424 425 </li>
425 426 % endif
426 427 ## render extra user menu
427 428 ${usermenu()}
428 429 </ul>
429 430
430 431 <script type="text/javascript">
431 432 var visual_show_public_icon = "${c.visual.show_public_icon}" == "True";
432 433
433 434 /*format the look of items in the list*/
434 435 var format = function(state, escapeMarkup){
435 436 if (!state.id){
436 437 return state.text; // optgroup
437 438 }
438 439 var obj_dict = state.obj;
439 440 var tmpl = '';
440 441
441 442 if(obj_dict && state.type == 'repo'){
442 443 if(obj_dict['repo_type'] === 'hg'){
443 444 tmpl += '<i class="icon-hg"></i> ';
444 445 }
445 446 else if(obj_dict['repo_type'] === 'git'){
446 447 tmpl += '<i class="icon-git"></i> ';
447 448 }
448 449 else if(obj_dict['repo_type'] === 'svn'){
449 450 tmpl += '<i class="icon-svn"></i> ';
450 451 }
451 452 if(obj_dict['private']){
452 453 tmpl += '<i class="icon-lock" ></i> ';
453 454 }
454 455 else if(visual_show_public_icon){
455 456 tmpl += '<i class="icon-unlock-alt"></i> ';
456 457 }
457 458 }
458 459 if(obj_dict && state.type == 'commit') {
459 460 tmpl += '<i class="icon-tag"></i>';
460 461 }
461 462 if(obj_dict && state.type == 'group'){
462 463 tmpl += '<i class="icon-folder-close"></i> ';
463 464 }
464 465 tmpl += escapeMarkup(state.text);
465 466 return tmpl;
466 467 };
467 468
468 469 var formatResult = function(result, container, query, escapeMarkup) {
469 470 return format(result, escapeMarkup);
470 471 };
471 472
472 473 var formatSelection = function(data, container, escapeMarkup) {
473 474 return format(data, escapeMarkup);
474 475 };
475 476
476 477 $("#repo_switcher").select2({
477 478 cachedDataSource: {},
478 479 minimumInputLength: 2,
479 480 placeholder: '<div class="menulabel">${_('Go to')} <div class="show_more"></div></div>',
480 481 dropdownAutoWidth: true,
481 482 formatResult: formatResult,
482 483 formatSelection: formatSelection,
483 484 containerCssClass: "repo-switcher",
484 485 dropdownCssClass: "repo-switcher-dropdown",
485 486 escapeMarkup: function(m){
486 487 // don't escape our custom placeholder
487 488 if(m.substr(0,23) == '<div class="menulabel">'){
488 489 return m;
489 490 }
490 491
491 492 return Select2.util.escapeMarkup(m);
492 493 },
493 494 query: $.debounce(250, function(query){
494 495 self = this;
495 496 var cacheKey = query.term;
496 497 var cachedData = self.cachedDataSource[cacheKey];
497 498
498 499 if (cachedData) {
499 500 query.callback({results: cachedData.results});
500 501 } else {
501 502 $.ajax({
502 503 url: "${h.url('goto_switcher_data')}",
503 504 data: {'query': query.term},
504 505 dataType: 'json',
505 506 type: 'GET',
506 507 success: function(data) {
507 508 self.cachedDataSource[cacheKey] = data;
508 509 query.callback({results: data.results});
509 510 },
510 511 error: function(data, textStatus, errorThrown) {
511 512 alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText));
512 513 }
513 514 })
514 515 }
515 516 })
516 517 });
517 518
518 519 $("#repo_switcher").on('select2-selecting', function(e){
519 520 e.preventDefault();
520 521 window.location = e.choice.url;
521 522 });
522 523
523 524 ## Global mouse bindings ##
524 525
525 526 // general help "?"
526 527 Mousetrap.bind(['?'], function(e) {
527 528 $('#help_kb').modal({})
528 529 });
529 530
530 531 // / open the quick filter
531 532 Mousetrap.bind(['/'], function(e) {
532 533 $("#repo_switcher").select2("open");
533 534
534 535 // return false to prevent default browser behavior
535 536 // and stop event from bubbling
536 537 return false;
537 538 });
538 539
539 540 // general nav g + action
540 541 Mousetrap.bind(['g h'], function(e) {
541 542 window.location = pyroutes.url('home');
542 543 });
543 544 Mousetrap.bind(['g g'], function(e) {
544 545 window.location = pyroutes.url('gists', {'private':1});
545 546 });
546 547 Mousetrap.bind(['g G'], function(e) {
547 548 window.location = pyroutes.url('gists', {'public':1});
548 549 });
549 550 Mousetrap.bind(['n g'], function(e) {
550 551 window.location = pyroutes.url('new_gist');
551 552 });
552 553 Mousetrap.bind(['n r'], function(e) {
553 554 window.location = pyroutes.url('new_repo');
554 555 });
555 556
556 557 % if hasattr(c, 'repo_name') and hasattr(c, 'rhodecode_db_repo'):
557 558 // nav in repo context
558 559 Mousetrap.bind(['g s'], function(e) {
559 560 window.location = pyroutes.url('summary_home', {'repo_name': REPO_NAME});
560 561 });
561 562 Mousetrap.bind(['g c'], function(e) {
562 563 window.location = pyroutes.url('changelog_home', {'repo_name': REPO_NAME});
563 564 });
564 565 Mousetrap.bind(['g F'], function(e) {
565 566 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': '', 'search': '1'});
566 567 });
567 568 Mousetrap.bind(['g f'], function(e) {
568 569 window.location = pyroutes.url('files_home', {'repo_name': REPO_NAME, 'revision': '${c.rhodecode_db_repo.landing_rev[1]}', 'f_path': ''});
569 570 });
570 571 Mousetrap.bind(['g p'], function(e) {
571 572 window.location = pyroutes.url('pullrequest_show_all', {'repo_name': REPO_NAME});
572 573 });
573 574 Mousetrap.bind(['g o'], function(e) {
574 575 window.location = pyroutes.url('edit_repo', {'repo_name': REPO_NAME});
575 576 });
576 577 Mousetrap.bind(['g O'], function(e) {
577 578 window.location = pyroutes.url('edit_repo_perms', {'repo_name': REPO_NAME});
578 579 });
579 580 % endif
580 581
581 582 </script>
582 583 <script src="${h.url('/js/rhodecode/base/keyboard-bindings.js', ver=c.rhodecode_version_hash)}"></script>
583 584 </%def>
584 585
585 586 <div class="modal" id="help_kb" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
586 587 <div class="modal-dialog">
587 588 <div class="modal-content">
588 589 <div class="modal-header">
589 590 <button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
590 591 <h4 class="modal-title" id="myModalLabel">${_('Keyboard shortcuts')}</h4>
591 592 </div>
592 593 <div class="modal-body">
593 594 <div class="block-left">
594 595 <table class="keyboard-mappings">
595 596 <tbody>
596 597 <tr>
597 598 <th></th>
598 599 <th>${_('Site-wide shortcuts')}</th>
599 600 </tr>
600 601 <%
601 602 elems = [
602 603 ('/', 'Open quick search box'),
603 604 ('g h', 'Goto home page'),
604 605 ('g g', 'Goto my private gists page'),
605 606 ('g G', 'Goto my public gists page'),
606 607 ('n r', 'New repository page'),
607 608 ('n g', 'New gist page'),
608 609 ]
609 610 %>
610 611 %for key, desc in elems:
611 612 <tr>
612 613 <td class="keys">
613 614 <span class="key tag">${key}</span>
614 615 </td>
615 616 <td>${desc}</td>
616 617 </tr>
617 618 %endfor
618 619 </tbody>
619 620 </table>
620 621 </div>
621 622 <div class="block-left">
622 623 <table class="keyboard-mappings">
623 624 <tbody>
624 625 <tr>
625 626 <th></th>
626 627 <th>${_('Repositories')}</th>
627 628 </tr>
628 629 <%
629 630 elems = [
630 631 ('g s', 'Goto summary page'),
631 632 ('g c', 'Goto changelog page'),
632 633 ('g f', 'Goto files page'),
633 634 ('g F', 'Goto files page with file search activated'),
634 635 ('g p', 'Goto pull requests page'),
635 636 ('g o', 'Goto repository settings'),
636 637 ('g O', 'Goto repository permissions settings'),
637 638 ]
638 639 %>
639 640 %for key, desc in elems:
640 641 <tr>
641 642 <td class="keys">
642 643 <span class="key tag">${key}</span>
643 644 </td>
644 645 <td>${desc}</td>
645 646 </tr>
646 647 %endfor
647 648 </tbody>
648 649 </table>
649 650 </div>
650 651 </div>
651 652 <div class="modal-footer">
652 653 </div>
653 654 </div><!-- /.modal-content -->
654 655 </div><!-- /.modal-dialog -->
655 656 </div><!-- /.modal -->
@@ -1,415 +1,414 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%inherit file="/base/base.html"/>
4 4
5 5 <%def name="title()">
6 6 ${_('%s Changelog') % c.repo_name}
7 7 %if c.changelog_for_path:
8 8 /${c.changelog_for_path}
9 9 %endif
10 10 %if c.rhodecode_name:
11 11 &middot; ${h.branding(c.rhodecode_name)}
12 12 %endif
13 13 </%def>
14 14
15 15 <%def name="breadcrumbs_links()">
16 16 %if c.changelog_for_path:
17 17 /${c.changelog_for_path}
18 18 %endif
19 19 ${ungettext('showing %d out of %d commit', 'showing %d out of %d commits', c.showing_commits) % (c.showing_commits, c.total_cs)}
20 20 </%def>
21 21
22 22 <%def name="menu_bar_nav()">
23 23 ${self.menu_items(active='repositories')}
24 24 </%def>
25 25
26 26 <%def name="menu_bar_subnav()">
27 27 ${self.repo_menu(active='changelog')}
28 28 </%def>
29 29
30 30 <%def name="main()">
31 31
32 32 <div class="box">
33 33 <div class="title">
34 34 ${self.repo_page_title(c.rhodecode_db_repo)}
35 35 <ul class="links">
36 36 <li>
37 37 <a href="#" class="btn btn-small" id="rev_range_container" style="display:none;"></a>
38 38 %if c.rhodecode_db_repo.fork:
39 39 <span>
40 40 <a id="compare_fork_button"
41 41 title="${_('Compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}"
42 42 class="btn btn-small"
43 43 href="${h.url('compare_url',
44 44 repo_name=c.rhodecode_db_repo.fork.repo_name,
45 45 source_ref_type=c.rhodecode_db_repo.landing_rev[0],
46 46 source_ref=c.rhodecode_db_repo.landing_rev[1],
47 47 target_repo=c.repo_name,
48 48 target_ref_type='branch' if request.GET.get('branch') else c.rhodecode_db_repo.landing_rev[0],
49 49 target_ref=request.GET.get('branch') or c.rhodecode_db_repo.landing_rev[1],
50 50 merge=1)
51 51 }">
52 52 <i class="icon-loop"></i>
53 53 ${_('Compare fork with Parent (%s)' % c.rhodecode_db_repo.fork.repo_name)}
54 54 </a>
55 55 </span>
56 56 %endif
57 57
58 58 ## pr open link
59 59 %if h.is_hg(c.rhodecode_repo) or h.is_git(c.rhodecode_repo):
60 60 <span>
61 61 <a id="open_new_pull_request" class="btn btn-small btn-success" href="${h.url('pullrequest_home',repo_name=c.repo_name)}">
62 62 ${_('Open new pull request')}
63 63 </a>
64 64 </span>
65 65 %endif
66 66
67 67 ## clear selection
68 68 <div title="${_('Clear selection')}" class="btn" id="rev_range_clear" style="display:none">
69 69 ${_('Clear selection')}
70 70 </div>
71 71
72 72 </li>
73 73 </ul>
74 74 </div>
75 75
76 76 % if c.pagination:
77 77
78 78 <div class="graph-header">
79 79 <div id="filter_changelog">
80 80 ${h.hidden('branch_filter')}
81 81 %if c.selected_name:
82 82 <div class="btn btn-default" id="clear_filter" >
83 83 ${_('Clear filter')}
84 84 </div>
85 85 %endif
86 86 </div>
87 87 ${self.breadcrumbs('breadcrumbs_light')}
88 88 </div>
89 89
90 90 <div id="graph">
91 91 <div class="graph-col-wrapper">
92 92 <div id="graph_nodes">
93 93 <div id="graph_canvas" data-graph='${c.jsdata|n}'></div>
94 94 </div>
95 95 <div id="graph_content" class="main-content graph_full_width">
96 96
97 97 <div class="table">
98 98 <table id="changesets" class="rctable">
99 99 <tr>
100 100 <th></th>
101 101 <th></th>
102 102 <th>${_('Author')}</th>
103 103 <th>${_('Age')}</th>
104 104 <th></th>
105 105 <th>${_('Commit Message')}</th>
106 106 <th>${_('Commit')}</th>
107 107 <th></th>
108 108 <th>${_('Refs')}</th>
109 109 </tr>
110 110 <tbody>
111 111 %for cnt,commit in enumerate(c.pagination):
112 112 <tr id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
113 113
114 114 <td class="td-checkbox">
115 115 ${h.checkbox(commit.raw_id,class_="commit-range")}
116 116 </td>
117 117 <td class="td-status">
118 118
119 119 %if c.statuses.get(commit.raw_id):
120 120 <div class="changeset-status-ico">
121 121 %if c.statuses.get(commit.raw_id)[2]:
122 122 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}">
123 123 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
124 124 </a>
125 125 %else:
126 126 <a class="tooltip" title="${_('Commit status: %s') % h.commit_status_lbl(c.statuses.get(commit.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
127 127 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
128 128 </a>
129 129 %endif
130 130 </div>
131 131 %endif
132 132 </td>
133 133 <td class="td-user">
134 ${self.gravatar(h.email_or_none(commit.author))}
135 <span title="${commit.author}" class="user">${h.link_to_user(commit.author, length=22)}</span>
134 ${self.gravatar_with_user(commit.author)}
136 135 </td>
137 136 <td class="td-time">
138 137 ${h.age_component(commit.date)}
139 138 </td>
140 139
141 140 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}">
142 141 <div class="show_more_col">
143 142 <i class="show_more"></i>&nbsp;
144 143 </div>
145 144 </td>
146 145 <td class="mid td-description">
147 146 <div class="log-container truncate-wrap">
148 147 <div class="message truncate" id="c-${commit.raw_id}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
149 148 </div>
150 149 </td>
151 150
152 151 <td class="td-hash">
153 152 <code>
154 153 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}">
155 154 <span class="commit_hash">${h.show_id(commit)}</span>
156 155 </a>
157 156 </code>
158 157 </td>
159 158
160 159 <td class="td-comments comments-col">
161 160 %if c.comments.get(commit.raw_id):
162 161 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
163 162 ${len(c.comments[commit.raw_id])} <i class="icon-comment icon-comment-colored"></i>
164 163 </a>
165 164 %endif
166 165 </td>
167 166
168 167 <td class="td-tags tags-col truncate-wrap">
169 168 <div class="truncate tags-truncate" id="t-${commit.raw_id}">
170 169 ## branch
171 170 %if commit.branch:
172 171 <span class="branchtag tag" title="${_('Branch %s') % commit.branch}">
173 172 <a href="${h.url('changelog_home',repo_name=c.repo_name,branch=commit.branch)}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
174 173 </span>
175 174 %endif
176 175
177 176 ## bookmarks
178 177 %if h.is_hg(c.rhodecode_repo):
179 178 %for book in commit.bookmarks:
180 179 <span class="tag booktag" title="${_('Bookmark %s') % book}">
181 180 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
182 181 </span>
183 182 %endfor
184 183 %endif
185 184
186 185 ## tags
187 186 %for tag in commit.tags:
188 187 <span class="tagtag tag" title="${_('Tag %s') % tag}">
189 188 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
190 189 </span>
191 190 %endfor
192 191
193 192 </div>
194 193 </td>
195 194 </tr>
196 195 %endfor
197 196 </tbody>
198 197 </table>
199 198 </div>
200 199 </div>
201 200 </div>
202 201 <div class="pagination-wh pagination-left">
203 202 ${c.pagination.pager('$link_previous ~2~ $link_next')}
204 203 </div>
205 204
206 205 <script type="text/javascript" src="${h.url('/js/jquery.commits-graph.js')}"></script>
207 206 <script type="text/javascript">
208 207 var cache = {};
209 208 $(function(){
210 209
211 210 // Create links to commit ranges when range checkboxes are selected
212 211 var $commitCheckboxes = $('.commit-range');
213 212 // cache elements
214 213 var $commitRangeContainer = $('#rev_range_container');
215 214 var $commitRangeClear = $('#rev_range_clear');
216 215
217 216 var checkboxRangeSelector = function(e){
218 217 var selectedCheckboxes = [];
219 218 for (pos in $commitCheckboxes){
220 219 if($commitCheckboxes[pos].checked){
221 220 selectedCheckboxes.push($commitCheckboxes[pos]);
222 221 }
223 222 }
224 223 var open_new_pull_request = $('#open_new_pull_request');
225 224 if(open_new_pull_request){
226 225 var selected_changes = selectedCheckboxes.length;
227 226 if (selected_changes > 1 || selected_changes == 1 && templateContext.repo_type != 'hg') {
228 227 open_new_pull_request.hide();
229 228 } else {
230 229 if (selected_changes == 1) {
231 230 open_new_pull_request.html(_gettext('Open new pull request for selected commit'));
232 231 } else if (selected_changes == 0) {
233 232 open_new_pull_request.html(_gettext('Open new pull request'));
234 233 }
235 234 open_new_pull_request.show();
236 235 }
237 236 }
238 237
239 238 if (selectedCheckboxes.length>0){
240 239 var revEnd = selectedCheckboxes[0].name;
241 240 var revStart = selectedCheckboxes[selectedCheckboxes.length-1].name;
242 241 var url = pyroutes.url('changeset_home',
243 242 {'repo_name': '${c.repo_name}',
244 243 'revision': revStart+'...'+revEnd});
245 244
246 245 var link = (revStart == revEnd)
247 246 ? _gettext('Show selected commit __S')
248 247 : _gettext('Show selected commits __S ... __E');
249 248
250 249 link = link.replace('__S', revStart.substr(0,6));
251 250 link = link.replace('__E', revEnd.substr(0,6));
252 251
253 252 $commitRangeContainer
254 253 .attr('href',url)
255 254 .html(link)
256 255 .show();
257 256
258 257 $commitRangeClear.show();
259 258 var _url = pyroutes.url('pullrequest_home',
260 259 {'repo_name': '${c.repo_name}',
261 260 'commit': revEnd});
262 261 open_new_pull_request.attr('href', _url);
263 262 $('#compare_fork_button').hide();
264 263 } else {
265 264 $commitRangeContainer.hide();
266 265 $commitRangeClear.hide();
267 266
268 267 %if c.branch_name:
269 268 var _url = pyroutes.url('pullrequest_home',
270 269 {'repo_name': '${c.repo_name}',
271 270 'branch':'${c.branch_name}'});
272 271 open_new_pull_request.attr('href', _url);
273 272 %else:
274 273 var _url = pyroutes.url('pullrequest_home',
275 274 {'repo_name': '${c.repo_name}'});
276 275 open_new_pull_request.attr('href', _url);
277 276 %endif
278 277 $('#compare_fork_button').show();
279 278 }
280 279 };
281 280
282 281 $commitCheckboxes.on('click', checkboxRangeSelector);
283 282
284 283 $commitRangeClear.on('click',function(e) {
285 284 $commitCheckboxes.attr('checked', false)
286 285 checkboxRangeSelector();
287 286 e.preventDefault();
288 287 });
289 288
290 289 // make sure the buttons are consistent when navigate back and forth
291 290 checkboxRangeSelector();
292 291
293 292
294 293 var msgs = $('.message');
295 294 // get first element height
296 295 var el = $('#graph_content .container')[0];
297 296 var row_h = el.clientHeight;
298 297 for (var i=0; i < msgs.length; i++) {
299 298 var m = msgs[i];
300 299
301 300 var h = m.clientHeight;
302 301 var pad = $(m).css('padding');
303 302 if (h > row_h) {
304 303 var offset = row_h - (h+12);
305 304 $(m.nextElementSibling).css('display','block');
306 305 $(m.nextElementSibling).css('margin-top',offset+'px');
307 306 }
308 307 }
309 308
310 309 $('.expand_commit').on('click',function(e){
311 310 var target_expand = $(this);
312 311 var cid = target_expand.data('commitId');
313 312
314 313 if (target_expand.hasClass('open')){
315 314 $('#c-'+cid).css({'height': '1.5em', 'white-space': 'nowrap', 'text-overflow': 'ellipsis', 'overflow':'hidden'});
316 315 $('#t-'+cid).css({'height': 'auto', 'line-height': '.9em', 'text-overflow': 'ellipsis', 'overflow':'hidden', 'white-space':'nowrap'});
317 316 target_expand.removeClass('open');
318 317 }
319 318 else {
320 319 $('#c-'+cid).css({'height': 'auto', 'white-space': 'pre-line', 'text-overflow': 'initial', 'overflow':'visible'});
321 320 $('#t-'+cid).css({'height': 'auto', 'max-height': 'none', 'text-overflow': 'initial', 'overflow':'visible', 'white-space':'normal'});
322 321 target_expand.addClass('open');
323 322 }
324 323 // redraw the graph
325 324 graph_options.height = $("#changesets").height();
326 325 $("canvas").remove();
327 326 $("[data-graph]").commits(graph_options);
328 327 });
329 328
330 329 $("#clear_filter").on("click", function() {
331 330 var filter = {'repo_name': '${c.repo_name}'};
332 331 window.location = pyroutes.url('changelog_home', filter);
333 332 });
334 333
335 334 $("#branch_filter").select2({
336 335 'dropdownAutoWidth': true,
337 336 'width': 'resolve',
338 337 'placeholder': "${c.selected_name or _('Filter changelog')}",
339 338 containerCssClass: "drop-menu",
340 339 dropdownCssClass: "drop-menu-dropdown",
341 340 query: function(query){
342 341 var key = 'cache';
343 342 var cached = cache[key] ;
344 343 if(cached) {
345 344 var data = {results: []};
346 345 //filter results
347 346 $.each(cached.results, function(){
348 347 var section = this.text;
349 348 var children = [];
350 349 $.each(this.children, function(){
351 350 if(query.term.length == 0 || this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ){
352 351 children.push({'id': this.id, 'text': this.text, 'type': this.type})
353 352 }
354 353 });
355 354 data.results.push({'text': section, 'children': children});
356 355 query.callback({results: data.results});
357 356 });
358 357 }else{
359 358 $.ajax({
360 359 url: pyroutes.url('repo_refs_changelog_data', {'repo_name': '${c.repo_name}'}),
361 360 data: {},
362 361 dataType: 'json',
363 362 type: 'GET',
364 363 success: function(data) {
365 364 cache[key] = data;
366 365 query.callback({results: data.results});
367 366 }
368 367 })
369 368 }
370 369 }
371 370 });
372 371
373 372 $('#branch_filter').on('change', function(e){
374 373 var data = $('#branch_filter').select2('data');
375 374 var selected = data.text;
376 375 var filter = {'repo_name': '${c.repo_name}'};
377 376 if(data.type == 'branch' || data.type == 'branch_closed'){
378 377 filter["branch"] = selected;
379 378 }
380 379 else if (data.type == 'book'){
381 380 filter["bookmark"] = selected;
382 381 }
383 382 window.location = pyroutes.url('changelog_home', filter);
384 383 });
385 384
386 385 // Determine max number of edges per row in graph
387 386 var jsdata = $.parseJSON($("[data-graph]").attr('data-graph'));
388 387 var edgeCount = 1;
389 388 $.each(jsdata, function(i, item){
390 389 $.each(item[2], function(key, value) {
391 390 if (value[1] > edgeCount){
392 391 edgeCount = value[1];
393 392 }
394 393 });
395 394 });
396 395 var x_step = Math.min(18, Math.floor(86 / edgeCount));
397 396 var graph_options = {
398 397 width: 100,
399 398 height: $("#changesets").height(),
400 399 x_step: x_step,
401 400 y_step: 42,
402 401 dotRadius: 3.5,
403 402 lineWidth: 2.5
404 403 };
405 404 $("[data-graph]").commits(graph_options);
406 405
407 406 });
408 407
409 408 </script>
410 409 %else:
411 410 ${_('There are no changes yet')}
412 411 %endif
413 412 </div>
414 413 </div>
415 414 </%def>
@@ -1,127 +1,126 b''
1 1 ## -*- coding: utf-8 -*-
2 2 <%namespace name="base" file="/base/base.html"/>
3 3 %if c.repo_commits:
4 4 <table class="rctable repo_summary table_disp">
5 5 <tr>
6 6 <th>${_('Commit')}</th>
7 7 <th class="status" colspan="2"></th>
8 8 <th>${_('Commit message')}</th>
9 9 <th>${_('Age')}</th>
10 10 <th>${_('Author')}</th>
11 11 <th>${_('Refs')}</th>
12 12 </tr>
13 13 %for cnt,cs in enumerate(c.repo_commits):
14 14 <tr class="parity${cnt%2}">
15 15 <td class="td-commit">
16 16 <pre><a href="${h.url('changeset_home', repo_name=c.repo_name, revision=cs.raw_id)}">${h.show_id(cs)}</a></pre>
17 17 </td>
18 18 <td class="td-status">
19 19 %if c.statuses.get(cs.raw_id):
20 20 <div class="changeset-status-ico shortlog">
21 21 %if c.statuses.get(cs.raw_id)[2]:
22 22 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (c.statuses.get(cs.raw_id)[0], c.statuses.get(cs.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}">
23 23 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
24 24 </a>
25 25 %else:
26 26 <div class="${'flag_status %s' % c.statuses.get(cs.raw_id)[0]}"></div>
27 27 %endif
28 28 </div>
29 29 %endif
30 30 </td>
31 31 <td class="td-comments">
32 32 %if c.comments.get(cs.raw_id,[]):
33 33 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
34 34 <i class="icon-comment icon-comment-colored"></i> ${len(c.comments[cs.raw_id])}
35 35 </a>
36 36 %endif
37 37 </td>
38 38 <td class="td-message">
39 39 ${h.urlify_commit_message(h.truncate(cs.message, 50), c.repo_name)}
40 40 </td>
41 41 <td class="td-time">
42 42 ${h.age_component(cs.date)}
43 43 </td>
44 44
45 45 <td class="td-user author">
46 ${base.gravatar(h.email_or_none(cs.author), 16)}
47 <span title="${cs.author}" class="user">${h.link_to_user(cs.author, length=22)}</span>
46 ${base.gravatar_with_user(cs.author)}
48 47 </td>
49 48 <td class="td-tags truncate-wrap">
50 49 <div class="truncate tags-truncate"><div class="autoexpand">
51 50 %if h.is_hg(c.rhodecode_repo):
52 51 %for book in cs.bookmarks:
53 52 <span class="booktag tag" title="${_('Bookmark %s') % book}">
54 53 <a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
55 54 </span>
56 55 %endfor
57 56 %endif
58 57 ## tags
59 58 %for tag in cs.tags:
60 59 <span class="tagtag tag" title="${_('Tag %s') % tag}">
61 60 <a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
62 61 </span>
63 62 %endfor
64 63
65 64 ## branch
66 65 %if cs.branch:
67 66 <span class="branchtag tag" title="${_('Branch %s') % cs.branch}">
68 67 <a href="${h.url('changelog_home',repo_name=c.repo_name,branch=cs.branch)}"><i class="icon-code-fork"></i>${h.shorter(cs.branch)}</a>
69 68 </span>
70 69 %endif
71 70 </div>
72 71 </td>
73 72 </tr>
74 73 %endfor
75 74
76 75 </table>
77 76
78 77 <script type="text/javascript">
79 78 $(document).pjax('#shortlog_data .pager_link','#shortlog_data', {timeout: 2000, scrollTo: false });
80 79 $(document).on('pjax:success', function(){ timeagoActivate(); });
81 80 </script>
82 81
83 82 <div class="pagination-wh pagination-left">
84 83 ${c.repo_commits.pager('$link_previous ~2~ $link_next')}
85 84 </div>
86 85 %else:
87 86
88 87 %if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
89 88 <div class="quick_start">
90 89 <div class="fieldset">
91 90 <div class="left-label">${_('Add or upload files directly via RhodeCode:')}</div>
92 91 <div class="right-content">
93 92 <div id="add_node_id" class="add_node">
94 93 <a href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='', anchor='edit')}" class="btn btn-default">${_('Add New File')}</a>
95 94 </div>
96 95 </div>
97 96 %endif
98 97 </div>
99 98
100 99 %if not h.is_svn(c.rhodecode_repo):
101 100 <div class="fieldset">
102 101 <div class="left-label">${_('Push new repo:')}</div>
103 102 <div class="right-content">
104 103 <pre>
105 104 ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
106 105 ${c.rhodecode_repo.alias} add README # add first file
107 106 ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
108 107 ${c.rhodecode_repo.alias} push ${'origin master' if h.is_git(c.rhodecode_repo) else ''} # push changes back
109 108 </pre>
110 109 </div>
111 110 </div>
112 111 <div class="fieldset">
113 112 <div class="left-label">${_('Existing repository?')}</div>
114 113 <div class="right-content">
115 114 <pre>
116 115 %if h.is_git(c.rhodecode_repo):
117 116 git remote add origin ${c.clone_repo_url}
118 117 git push -u origin master
119 118 %else:
120 119 hg push ${c.clone_repo_url}
121 120 %endif
122 121 </pre>
123 122 </div>
124 123 </div>
125 124 %endif
126 125 </div>
127 126 %endif
@@ -1,395 +1,393 b''
1 1 ## -*- coding: utf-8 -*-
2 2
3 3 <%inherit file="/base/base.html"/>
4 4 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
5 5
6 6 <%def name="title()">
7 7 ${_('%s Commit') % c.repo_name} - ${h.show_id(c.commit)}
8 8 %if c.rhodecode_name:
9 9 &middot; ${h.branding(c.rhodecode_name)}
10 10 %endif
11 11 </%def>
12 12
13 13 <%def name="menu_bar_nav()">
14 14 ${self.menu_items(active='repositories')}
15 15 </%def>
16 16
17 17 <%def name="menu_bar_subnav()">
18 18 ${self.repo_menu(active='changelog')}
19 19 </%def>
20 20
21 21 <%def name="main()">
22 22 <script>
23 23 // TODO: marcink switch this to pyroutes
24 24 AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
25 25 templateContext.commit_data.commit_id = "${c.commit.raw_id}";
26 26 </script>
27 27 <div class="box">
28 28 <div class="title">
29 29 ${self.repo_page_title(c.rhodecode_db_repo)}
30 30 </div>
31 31
32 32 <div id="changeset_compare_view_content" class="summary changeset">
33 33 <div class="summary-detail">
34 34 <div class="summary-detail-header">
35 35 <span class="breadcrumbs files_location">
36 36 <h4>${_('Commit')}
37 37 <code>
38 38 ${h.show_id(c.commit)}
39 39 </code>
40 40 </h4>
41 41 </span>
42 42 <span id="parent_link">
43 43 <a href="#" title="${_('Parent Commit')}">${_('Parent')}</a>
44 44 </span>
45 45 |
46 46 <span id="child_link">
47 47 <a href="#" title="${_('Child Commit')}">${_('Child')}</a>
48 48 </span>
49 49 </div>
50 50
51 51 <div class="fieldset">
52 52 <div class="left-label">
53 53 ${_('Description')}:
54 54 </div>
55 55 <div class="right-content">
56 56 <div id="trimmed_message_box" class="commit">${h.urlify_commit_message(c.commit.message,c.repo_name)}</div>
57 57 <div id="message_expand" style="display:none;">
58 58 ${_('Expand')}
59 59 </div>
60 60 </div>
61 61 </div>
62 62
63 63 %if c.statuses:
64 64 <div class="fieldset">
65 65 <div class="left-label">
66 66 ${_('Commit status')}:
67 67 </div>
68 68 <div class="right-content">
69 69 <div class="changeset-status-ico">
70 70 <div class="${'flag_status %s' % c.statuses[0]} pull-left"></div>
71 71 </div>
72 72 <div title="${_('Commit status')}" class="changeset-status-lbl">[${h.commit_status_lbl(c.statuses[0])}]</div>
73 73 </div>
74 74 </div>
75 75 %endif
76 76
77 77 <div class="fieldset">
78 78 <div class="left-label">
79 79 ${_('References')}:
80 80 </div>
81 81 <div class="right-content">
82 82 <div class="tags">
83 83
84 84 %if c.commit.merge:
85 85 <span class="mergetag tag">
86 86 ${_('merge')}
87 87 </span>
88 88 %endif
89 89
90 90 %if h.is_hg(c.rhodecode_repo):
91 91 %for book in c.commit.bookmarks:
92 92 <span class="booktag tag" title="${_('Bookmark %s') % book}">
93 93 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
94 94 </span>
95 95 %endfor
96 96 %endif
97 97
98 98 %for tag in c.commit.tags:
99 99 <span class="tagtag tag" title="${_('Tag %s') % tag}">
100 100 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-tag"></i>${tag}</a>
101 101 </span>
102 102 %endfor
103 103
104 104 %if c.commit.branch:
105 105 <span class="branchtag tag" title="${_('Branch %s') % c.commit.branch}">
106 106 <a href="${h.url('files_home',repo_name=c.repo_name,revision=c.commit.raw_id)}"><i class="icon-code-fork"></i>${h.shorter(c.commit.branch)}</a>
107 107 </span>
108 108 %endif
109 109 </div>
110 110 </div>
111 111 </div>
112 112
113 113 <div class="fieldset">
114 114 <div class="left-label">
115 115 ${_('Diffs')}:
116 116 </div>
117 117 <div class="right-content">
118 118 <div class="diff-actions">
119 119 <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Raw diff'))}">
120 120 ${_('Raw Diff')}
121 121 </a>
122 122 |
123 123 <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision=c.commit.raw_id)}" class="tooltip" title="${h.tooltip(_('Patch diff'))}">
124 124 ${_('Patch Diff')}
125 125 </a>
126 126 |
127 127 <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision=c.commit.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('Download diff'))}">
128 128 ${_('Download Diff')}
129 129 </a>
130 130 |
131 131 ${c.ignorews_url(request.GET)}
132 132 |
133 133 ${c.context_url(request.GET)}
134 134 </div>
135 135 </div>
136 136 </div>
137 137
138 138 <div class="fieldset">
139 139 <div class="left-label">
140 140 ${_('Comments')}:
141 141 </div>
142 142 <div class="right-content">
143 143 <div class="comments-number">
144 144 %if c.comments:
145 145 <a href="#comments">${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}</a>,
146 146 %else:
147 147 ${ungettext("%d Commit comment", "%d Commit comments", len(c.comments)) % len(c.comments)}
148 148 %endif
149 149 %if c.inline_cnt:
150 150 ## this is replaced with a proper link to first comment via JS linkifyComments() func
151 151 <a href="#inline-comments" id="inline-comments-counter">${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}</a>
152 152 %else:
153 153 ${ungettext("%d Inline Comment", "%d Inline Comments", c.inline_cnt) % c.inline_cnt}
154 154 %endif
155 155 </div>
156 156 </div>
157 157 </div>
158 158
159 159 </div> <!-- end summary-detail -->
160 160
161 161 <div id="commit-stats" class="sidebar-right">
162 162 <div class="summary-detail-header">
163 163 <h4 class="item">
164 164 ${_('Author')}
165 165 </h4>
166 166 </div>
167 <div class="sidebar-right-content">
168 <img alt="gravatar" class="gravatar" title="${h.email_or_none(c.commit.author)}" src="${h.gravatar_url(h.email_or_none(c.commit.author), 40)}" height="16" width="16">
169 <span class="author">
170 ${h.link_to_user(c.commit.author)} - ${h.age_component(c.commit.date)}
171 </span>
172 </div>
167 <div class="sidebar-right-content">
168 ${self.gravatar_with_user(c.commit.author)}
169 <div class="user-inline-data">- ${h.age_component(c.commit.date)}</div>
170 </div>
173 171 </div><!-- end sidebar -->
174 172 </div> <!-- end summary -->
175 173 <div class="cs_files_title">
176 174 <span class="cs_files_expand">
177 175 <span id="files_link"><a href="#" title="${_('Browse files at current commit')}">${_('Browse files')}</a></span> |
178 176
179 177 <span id="expand_all_files">${_('Expand All')}</span> | <span id="collapse_all_files">${_('Collapse All')}</span>
180 178 </span>
181 179 <h2>
182 180 ${diff_block.diff_summary_text(len(c.files), c.lines_added, c.lines_deleted, c.limited_diff)}
183 181 </h2>
184 182 </div>
185 183 </div>
186 184
187 185 <div class="cs_files">
188 186
189 187 %if not c.files:
190 188 <p class="empty_data">${_('No files')}</p>
191 189 %endif
192 190
193 191 <table class="compare_view_files commit_diff">
194 192 %for FID, (cs1, cs2, change, path, diff, stats, file) in c.changes[c.commit.raw_id].iteritems():
195 193 <tr class="cs_${change} collapse_file" fid="${FID}">
196 194 <td class="cs_icon_td">
197 195 <span class="collapse_file_icon" fid="${FID}"></span>
198 196 </td>
199 197 <td class="cs_icon_td">
200 198 <div class="flag_status not_reviewed hidden"></div>
201 199 </td>
202 200 <td class="cs_${change}" id="a_${FID}">
203 201 <div class="node">
204 202 <a href="#a_${FID}">
205 203 <i class="icon-file-${change.lower()}"></i>
206 204 ${h.safe_unicode(path)}
207 205 </a>
208 206 </div>
209 207 </td>
210 208 <td>
211 209 <div class="changes pull-right">${h.fancy_file_stats(stats)}</div>
212 210 <div class="comment-bubble pull-right" data-path="${path}">
213 211 <i class="icon-comment"></i>
214 212 </div>
215 213 </td>
216 214 </tr>
217 215 <tr fid="${FID}" id="diff_${FID}" class="diff_links">
218 216 <td></td>
219 217 <td></td>
220 218 <td class="cs_${change}">
221 219 ${diff_block.diff_menu(c.repo_name, h.safe_unicode(path), cs1, cs2, change, file)}
222 220 </td>
223 221 <td class="td-actions rc-form">
224 222 ${c.ignorews_url(request.GET, h.FID(cs2,path))} |
225 223 ${c.context_url(request.GET, h.FID(cs2,path))} |
226 224 <div data-comment-id="${h.FID(cs2,path)}" class="btn-link show-inline-comments comments-visible">
227 225 <span class="comments-show">${_('Show comments')}</span>
228 226 <span class="comments-hide">${_('Hide comments')}</span>
229 227 </div>
230 228 </td>
231 229 </tr>
232 230 <tr id="tr_${FID}">
233 231 <td></td>
234 232 <td></td>
235 233 <td class="injected_diff" colspan="2">
236 234 <div class="diff-container" id="${'diff-container-%s' % (id(change))}">
237 235 <div id="${FID}" class="diffblock margined comm">
238 236 <div class="code-body">
239 237 <div class="full_f_path" path="${h.safe_unicode(path)}"></div>
240 238 ${diff|n}
241 239 % if file and file["is_limited_diff"]:
242 240 % if file["exceeds_limit"]:
243 241 ${diff_block.file_message()}
244 242 % else:
245 243 <h5>${_('Diff was truncated. File content available only in full diff.')} <a href="${h.url.current(fulldiff=1, **request.GET.mixed())}" onclick="return confirm('${_("Showing a big diff might take some time and resources, continue?")}')">${_('Show full diff')}</a></h5>
246 244 % endif
247 245 % endif
248 246 </div>
249 247 </div>
250 248 </div>
251 249 </td>
252 250 </tr>
253 251 %endfor
254 252 </table>
255 253 </div>
256 254
257 255 % if c.limited_diff:
258 256 ${diff_block.changeset_message()}
259 257 % endif
260 258
261 259 ## template for inline comment form
262 260 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
263 261 ${comment.comment_inline_form()}
264 262
265 263 ## render comments and inlines
266 264 ${comment.generate_comments()}
267 265
268 266 ## main comment form and it status
269 267 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.commit.raw_id),
270 268 h.commit_status(c.rhodecode_db_repo, c.commit.raw_id))}
271 269
272 270 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
273 271 <script type="text/javascript">
274 272
275 273 $(document).ready(function() {
276 274
277 275 var boxmax = parseInt($('#trimmed_message_box').css('max-height'), 10);
278 276 if($('#trimmed_message_box').height() === boxmax){
279 277 $('#message_expand').show();
280 278 }
281 279
282 280 $('#message_expand').on('click', function(e){
283 281 $('#trimmed_message_box').css('max-height', 'none');
284 282 $(this).hide();
285 283 });
286 284
287 285 $('.show-inline-comments').on('click', function(e){
288 286 var boxid = $(this).attr('data-comment-id');
289 287 var button = $(this);
290 288
291 289 if(button.hasClass("comments-visible")) {
292 290 $('#{0} .inline-comments'.format(boxid)).each(function(index){
293 291 $(this).hide();
294 292 })
295 293 button.removeClass("comments-visible");
296 294 } else {
297 295 $('#{0} .inline-comments'.format(boxid)).each(function(index){
298 296 $(this).show();
299 297 })
300 298 button.addClass("comments-visible");
301 299 }
302 300 });
303 301
304 302
305 303 // next links
306 304 $('#child_link').on('click', function(e){
307 305 // fetch via ajax what is going to be the next link, if we have
308 306 // >1 links show them to user to choose
309 307 if(!$('#child_link').hasClass('disabled')){
310 308 $.ajax({
311 309 url: '${h.url('changeset_children',repo_name=c.repo_name, revision=c.commit.raw_id)}',
312 310 success: function(data) {
313 311 if(data.results.length === 0){
314 312 $('#child_link').html('${_('No Child Commits')}').addClass('disabled');
315 313 }
316 314 if(data.results.length === 1){
317 315 var commit = data.results[0];
318 316 window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
319 317 }
320 318 else if(data.results.length === 2){
321 319 $('#child_link').addClass('disabled');
322 320 $('#child_link').addClass('double');
323 321 var _html = '';
324 322 _html +='<a title="__title__" href="__url__">__rev__</a> '
325 323 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
326 324 .replace('__title__', data.results[0].message)
327 325 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
328 326 _html +=' | '
329 327 _html +='<a title="__title__" href="__url__">__rev__</a> '
330 328 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
331 329 .replace('__title__', data.results[1].message)
332 330 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
333 331 $('#child_link').html(_html);
334 332 }
335 333 }
336 334 });
337 335 e.preventDefault();
338 336 }
339 337 });
340 338
341 339 // prev links
342 340 $('#parent_link').on('click', function(e){
343 341 // fetch via ajax what is going to be the next link, if we have
344 342 // >1 links show them to user to choose
345 343 if(!$('#parent_link').hasClass('disabled')){
346 344 $.ajax({
347 345 url: '${h.url('changeset_parents',repo_name=c.repo_name, revision=c.commit.raw_id)}',
348 346 success: function(data) {
349 347 if(data.results.length === 0){
350 348 $('#parent_link').html('${_('No Parent Commits')}').addClass('disabled');
351 349 }
352 350 if(data.results.length === 1){
353 351 var commit = data.results[0];
354 352 window.location = pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': commit.raw_id});
355 353 }
356 354 else if(data.results.length === 2){
357 355 $('#parent_link').addClass('disabled');
358 356 $('#parent_link').addClass('double');
359 357 var _html = '';
360 358 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
361 359 .replace('__rev__','r{0}:{1}'.format(data.results[0].revision, data.results[0].raw_id.substr(0,6)))
362 360 .replace('__title__', data.results[0].message)
363 361 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[0].raw_id}));
364 362 _html +=' | '
365 363 _html +='<a title="__title__" href="__url__">Parent __rev__</a>'
366 364 .replace('__rev__','r{0}:{1}'.format(data.results[1].revision, data.results[1].raw_id.substr(0,6)))
367 365 .replace('__title__', data.results[1].message)
368 366 .replace('__url__', pyroutes.url('changeset_home', {'repo_name': '${c.repo_name}','revision': data.results[1].raw_id}));
369 367 $('#parent_link').html(_html);
370 368 }
371 369 }
372 370 });
373 371 e.preventDefault();
374 372 }
375 373 });
376 374
377 375 if (location.href.indexOf('#') != -1) {
378 376 var id = '#'+location.href.substring(location.href.indexOf('#') + 1).split('#');
379 377 var line = $('html').find(id);
380 378 offsetScroll(line, 70);
381 379 }
382 380
383 381 // browse tree @ revision
384 382 $('#files_link').on('click', function(e){
385 383 window.location = '${h.url('files_home',repo_name=c.repo_name, revision=c.commit.raw_id, f_path='')}';
386 384 e.preventDefault();
387 385 });
388 386
389 387 // inject comments into their proper positions
390 388 var file_comments = $('.inline-comment-placeholder');
391 389 renderInlineComments(file_comments, true);
392 390 })
393 391 </script>
394 392
395 393 </%def>
@@ -1,297 +1,299 b''
1 1 ## DATA TABLE RE USABLE ELEMENTS
2 2 ## usage:
3 3 ## <%namespace name="dt" file="/data_table/_dt_elements.html"/>
4 4 <%namespace name="base" file="/base/base.html"/>
5 5
6 6 ## REPOSITORY RENDERERS
7 7 <%def name="quick_menu(repo_name)">
8 8 <i class="pointer icon-more"></i>
9 9 <div class="menu_items_container hidden">
10 10 <ul class="menu_items">
11 11 <li>
12 12 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo_name)}">
13 13 <span>${_('Summary')}</span>
14 14 </a>
15 15 </li>
16 16 <li>
17 17 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo_name)}">
18 18 <span>${_('Changelog')}</span>
19 19 </a>
20 20 </li>
21 21 <li>
22 22 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo_name)}">
23 23 <span>${_('Files')}</span>
24 24 </a>
25 25 </li>
26 26 <li>
27 27 <a title="${_('Fork')}" href="${h.url('repo_fork_home',repo_name=repo_name)}">
28 28 <span>${_('Fork')}</span>
29 29 </a>
30 30 </li>
31 31 </ul>
32 32 </div>
33 33 </%def>
34 34
35 35 <%def name="repo_name(name,rtype,rstate,private,fork_of,short_name=False,admin=False)">
36 36 <%
37 37 def get_name(name,short_name=short_name):
38 38 if short_name:
39 39 return name.split('/')[-1]
40 40 else:
41 41 return name
42 42 %>
43 43 <div class="${'repo_state_pending' if rstate == 'repo_state_pending' else ''} truncate">
44 44 ##NAME
45 45 <a href="${h.url('edit_repo' if admin else 'summary_home',repo_name=name)}">
46 46
47 47 ##TYPE OF REPO
48 48 %if h.is_hg(rtype):
49 49 <span title="${_('Mercurial repository')}"><i class="icon-hg"></i></span>
50 50 %elif h.is_git(rtype):
51 51 <span title="${_('Git repository')}"><i class="icon-git"></i></span>
52 52 %elif h.is_svn(rtype):
53 53 <span title="${_('Subversion repository')}"><i class="icon-svn"></i></span>
54 54 %endif
55 55
56 56 ##PRIVATE/PUBLIC
57 57 %if private and c.visual.show_private_icon:
58 58 <i class="icon-lock" title="${_('Private repository')}"></i>
59 59 %elif not private and c.visual.show_public_icon:
60 60 <i class="icon-unlock-alt" title="${_('Public repository')}"></i>
61 61 %else:
62 62 <span></span>
63 63 %endif
64 64 ${get_name(name)}
65 65 </a>
66 66 %if fork_of:
67 67 <a href="${h.url('summary_home',repo_name=fork_of.repo_name)}"><i class="icon-code-fork"></i></a>
68 68 %endif
69 69 %if rstate == 'repo_state_pending':
70 70 <i class="icon-cogs" title="${_('Repository creating in progress...')}"></i>
71 71 %endif
72 72 </div>
73 73 </%def>
74 74
75 75 <%def name="last_change(last_change)">
76 76 ${h.age_component(last_change)}
77 77 </%def>
78 78
79 79 <%def name="revision(name,rev,tip,author,last_msg)">
80 80 <div>
81 81 %if rev >= 0:
82 82 <code><a title="${h.tooltip('%s:\n\n%s' % (author,last_msg))}" class="tooltip" href="${h.url('changeset_home',repo_name=name,revision=tip)}">${'r%s:%s' % (rev,h.short_id(tip))}</a></code>
83 83 %else:
84 84 ${_('No commits yet')}
85 85 %endif
86 86 </div>
87 87 </%def>
88 88
89 89 <%def name="rss(name)">
90 90 %if c.rhodecode_user.username != h.DEFAULT_USER:
91 91 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
92 92 %else:
93 93 <a title="${_('Subscribe to %s rss feed')% name}" href="${h.url('rss_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
94 94 %endif
95 95 </%def>
96 96
97 97 <%def name="atom(name)">
98 98 %if c.rhodecode_user.username != h.DEFAULT_USER:
99 99 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name,auth_token=c.rhodecode_user.feed_token)}"><i class="icon-rss-sign"></i></a>
100 100 %else:
101 101 <a title="${_('Subscribe to %s atom feed')% name}" href="${h.url('atom_feed_home',repo_name=name)}"><i class="icon-rss-sign"></i></a>
102 102 %endif
103 103 </%def>
104 104
105 105 <%def name="user_gravatar(email, size=16)">
106 <div class="rc-user tooltip" title="${h.author_string(email)}">
106 107 ${base.gravatar(email, 16)}
108 </div>
107 109 </%def>
108 110
109 111 <%def name="repo_actions(repo_name, super_user=True)">
110 112 <div>
111 113 <div class="grid_edit">
112 114 <a href="${h.url('edit_repo',repo_name=repo_name)}" title="${_('Edit')}">
113 115 <i class="icon-pencil"></i>Edit</a>
114 116 </div>
115 117 <div class="grid_delete">
116 118 ${h.secure_form(h.url('repo', repo_name=repo_name),method='delete')}
117 119 ${h.submit('remove_%s' % repo_name,_('Delete'),class_="btn btn-link btn-danger",
118 120 onclick="return confirm('"+_('Confirm to delete this repository: %s') % repo_name+"');")}
119 121 ${h.end_form()}
120 122 </div>
121 123 </div>
122 124 </%def>
123 125
124 126 <%def name="repo_state(repo_state)">
125 127 <div>
126 128 %if repo_state == 'repo_state_pending':
127 129 <div class="tag tag4">${_('Creating')}</div>
128 130 %elif repo_state == 'repo_state_created':
129 131 <div class="tag tag1">${_('Created')}</div>
130 132 %else:
131 133 <div class="tag alert2" title="${repo_state}">invalid</div>
132 134 %endif
133 135 </div>
134 136 </%def>
135 137
136 138
137 139 ## REPO GROUP RENDERERS
138 140 <%def name="quick_repo_group_menu(repo_group_name)">
139 141 <i class="pointer icon-more"></i>
140 142 <div class="menu_items_container hidden">
141 143 <ul class="menu_items">
142 144 <li>
143 145 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
144 146 <span class="icon">
145 147 <i class="icon-file-text"></i>
146 148 </span>
147 149 <span>${_('Summary')}</span>
148 150 </a>
149 151 </li>
150 152
151 153 </ul>
152 154 </div>
153 155 </%def>
154 156
155 157 <%def name="repo_group_name(repo_group_name, children_groups=None)">
156 158 <div>
157 159 <a href="${h.url('repo_group_home',group_name=repo_group_name)}">
158 160 <i class="icon-folder-close" title="${_('Repository group')}"></i>
159 161 %if children_groups:
160 162 ${h.literal(' &raquo; '.join(children_groups))}
161 163 %else:
162 164 ${repo_group_name}
163 165 %endif
164 166 </a>
165 167 </div>
166 168 </%def>
167 169
168 170 <%def name="repo_group_actions(repo_group_id, repo_group_name, gr_count)">
169 171 <div class="grid_edit">
170 172 <a href="${h.url('edit_repo_group',group_name=repo_group_name)}" title="${_('Edit')}">Edit</a>
171 173 </div>
172 174 <div class="grid_delete">
173 175 ${h.secure_form(h.url('delete_repo_group', group_name=repo_group_name),method='delete')}
174 176 ${h.submit('remove_%s' % repo_group_name,_('Delete'),class_="btn btn-link btn-danger",
175 177 onclick="return confirm('"+ungettext('Confirm to delete this group: %s with %s repository','Confirm to delete this group: %s with %s repositories',gr_count) % (repo_group_name, gr_count)+"');")}
176 178 ${h.end_form()}
177 179 </div>
178 180 </%def>
179 181
180 182
181 183 <%def name="user_actions(user_id, username)">
182 184 <div class="grid_edit">
183 185 <a href="${h.url('edit_user',user_id=user_id)}" title="${_('Edit')}">
184 186 <i class="icon-pencil"></i>Edit</a>
185 187 </div>
186 188 <div class="grid_delete">
187 189 ${h.secure_form(h.url('delete_user', user_id=user_id),method='delete')}
188 190 ${h.submit('remove_',_('Delete'),id="remove_user_%s" % user_id, class_="btn btn-link btn-danger",
189 191 onclick="return confirm('"+_('Confirm to delete this user: %s') % username+"');")}
190 192 ${h.end_form()}
191 193 </div>
192 194 </%def>
193 195
194 196 <%def name="user_group_actions(user_group_id, user_group_name)">
195 197 <div class="grid_edit">
196 198 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}" title="${_('Edit')}">Edit</a>
197 199 </div>
198 200 <div class="grid_delete">
199 201 ${h.secure_form(h.url('delete_users_group', user_group_id=user_group_id),method='delete')}
200 202 ${h.submit('remove_',_('Delete'),id="remove_group_%s" % user_group_id, class_="btn btn-link btn-danger",
201 203 onclick="return confirm('"+_('Confirm to delete this user group: %s') % user_group_name+"');")}
202 204 ${h.end_form()}
203 205 </div>
204 206 </%def>
205 207
206 208
207 209 <%def name="user_name(user_id, username)">
208 210 ${h.link_to(h.person(username, 'username_or_name_or_email'), h.url('edit_user', user_id=user_id))}
209 211 </%def>
210 212
211 213 <%def name="user_profile(username)">
212 214 ${base.gravatar_with_user(username, 16)}
213 215 </%def>
214 216
215 217 <%def name="user_group_name(user_group_id, user_group_name)">
216 218 <div>
217 219 <a href="${h.url('edit_users_group', user_group_id=user_group_id)}">
218 220 <i class="icon-group" title="${_('User group')}"></i> ${user_group_name}</a>
219 221 </div>
220 222 </%def>
221 223
222 224
223 225 ## GISTS
224 226
225 227 <%def name="gist_gravatar(full_contact)">
226 228 <div class="gist_gravatar">
227 229 ${base.gravatar(full_contact, 30)}
228 230 </div>
229 231 </%def>
230 232
231 233 <%def name="gist_access_id(gist_access_id, full_contact)">
232 234 <div>
233 235 <b>
234 236 <a href="${h.url('gist',gist_id=gist_access_id)}">gist: ${gist_access_id}</a>
235 237 </b>
236 238 </div>
237 239 </%def>
238 240
239 241 <%def name="gist_author(full_contact, created_on, expires)">
240 242 ${base.gravatar_with_user(full_contact, 16)}
241 243 </%def>
242 244
243 245
244 246 <%def name="gist_created(created_on)">
245 247 <div class="created">
246 248 ${h.age_component(created_on, time_is_local=True)}
247 249 </div>
248 250 </%def>
249 251
250 252 <%def name="gist_expires(expires)">
251 253 <div class="created">
252 254 %if expires == -1:
253 255 ${_('never')}
254 256 %else:
255 257 ${h.age_component(h.time_to_utcdatetime(expires))}
256 258 %endif
257 259 </div>
258 260 </%def>
259 261
260 262 <%def name="gist_type(gist_type)">
261 263 %if gist_type != 'public':
262 264 <div class="tag">${_('Private')}</div>
263 265 %endif
264 266 </%def>
265 267
266 268 <%def name="gist_description(gist_description)">
267 269 ${gist_description}
268 270 </%def>
269 271
270 272
271 273 ## PULL REQUESTS GRID RENDERERS
272 274 <%def name="pullrequest_status(status)">
273 275 <div class="${'flag_status %s' % status} pull-left"></div>
274 276 </%def>
275 277
276 278 <%def name="pullrequest_title(title, description)">
277 279 ${title} <br/>
278 280 ${h.shorter(description, 40)}
279 281 </%def>
280 282
281 283 <%def name="pullrequest_comments(comments_nr)">
282 284 <i class="icon-comment icon-comment-colored"></i> ${comments_nr}
283 285 </%def>
284 286
285 287 <%def name="pullrequest_name(pull_request_id, target_repo_name)">
286 288 <a href="${h.url('pullrequest_show',repo_name=target_repo_name,pull_request_id=pull_request_id)}">
287 289 ${_('Pull request #%(pr_number)s') % {'pr_number': pull_request_id,}}
288 290 </a>
289 291 </%def>
290 292
291 293 <%def name="pullrequest_updated_on(updated_on)">
292 294 ${h.age_component(h.time_to_utcdatetime(updated_on))}
293 295 </%def>
294 296
295 297 <%def name="pullrequest_author(full_contact)">
296 298 ${base.gravatar_with_user(full_contact, 16)}
297 299 </%def>
@@ -1,36 +1,30 b''
1 1 <%namespace name="base" file="/base/base.html"/>
2 2
3 3 <div class="summary-detail-header">
4 4 <h4 class="item">
5 5 % if c.file_author:
6 6 ${_('Last Author')}
7 7 % else:
8 8 ${h.literal(ungettext(u'File Author (%s)',u'File Authors (%s)',len(c.authors)) % ('<b>%s</b>' % len(c.authors))) }
9 9 % endif
10 10 </h4>
11 11 <a href="#" id="show_authors" class="action_link">${_('Show All')}</a>
12 12 </div>
13 13
14 14 % if c.authors:
15 <ul class="pull-left">
15 <ul class="sidebar-right-content">
16 16 % for email, user in sorted(c.authors, key=lambda e: c.file_last_commit.author_email!=e[0]):
17 17 <li class="file_author">
18 <div class="contributor tooltip" title="${h.tooltip(user)}">
19 ${base.gravatar(email, 16)}
20 <span class="author user">
21 ## case initial page load we only have last commit author
22 % if c.file_author:
23 ${h.link_to_user(user)} - ${h.age_component(c.file_last_commit.date)}
24 % else:
25 % if c.file_last_commit.author_email==email:
26 <strong>${h.link_to_user(user)}</strong> (${_('last author')})
27 % else:
28 ${h.link_to_user(user)}
29 % endif
30 % endif
31 </span>
32 </div>
18 <div class="rc-user tooltip" title="${h.author_string(email)}">
19 ${base.gravatar(email, 16)}
20 <span class="user">${h.link_to_user(user)}</span>
21 </div>
22 % if c.file_author:
23 <div class="user-inline-data">- ${h.age_component(c.file_last_commit.date)}</div>
24 % elif c.file_last_commit.author_email==email:
25 <div class="user-inline-data"> (${_('last author')})</div>
26 % endif
33 27 </li>
34 28 % endfor
35 29 </ul>
36 30 % endif
@@ -1,14 +1,12 b''
1 1 <%namespace name="base" file="/base/base.html"/>
2 2
3 3 <div class="summary-detail-header">
4 4 <h4 class="item">
5 5 ${_('Commit Author')}
6 6 </h4>
7 7 </div>
8 8 <div class="sidebar-right-content">
9 ${base.gravatar(h.email_or_none(c.commit.author), 16)}
10 <span class="author user">
11 ${h.link_to_user(c.commit.author)} - ${h.age_component(c.commit.date)}
12 </span>
9 ${base.gravatar_with_user(c.commit.author)}
10 <div class="user-inline-data">- ${h.age_component(c.commit.date)}</div>
13 11 </div>
14 12
General Comments 0
You need to be logged in to leave comments. Login now