"""Helper functions
Consists of functions to typically be used within templates, but also
available to Controllers. This module is available to both as 'h'.
"""
import random
import hashlib
import StringIO
import urllib
import math
import logging
from datetime import datetime
from pygments.formatters.html import HtmlFormatter
from pygments import highlight as code_highlight
from pylons import url, request, config
from pylons.i18n.translation import _, ungettext
from hashlib import md5
from webhelpers.html import literal, HTML, escape
from webhelpers.html.tools import *
from webhelpers.html.builder import make_tag
from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
end_form, file, form, hidden, image, javascript_link, link_to, \
link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
submit, text, password, textarea, title, ul, xml_declaration, radio
from webhelpers.html.tools import auto_link, button_to, highlight, \
js_obfuscate, mail_to, strip_links, strip_tags, tag_re
from webhelpers.number import format_byte_size, format_bit_size
from webhelpers.pylonslib import Flash as _Flash
from webhelpers.pylonslib.secure_form import secure_form
from webhelpers.text import chop_at, collapse, convert_accented_entities, \
convert_misc_entities, lchop, plural, rchop, remove_formatting, \
replace_whitespace, urlify, truncate, wrap_paragraphs
from webhelpers.date import time_ago_in_words
from webhelpers.paginate import Page
from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
convert_boolean_attrs, NotGiven, _make_safe_id_component
from rhodecode.lib.annotate import annotate_highlight
from rhodecode.lib.utils import repo_name_slug
from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
get_changeset_safe
from rhodecode.lib.markup_renderer import MarkupRenderer
log = logging.getLogger(__name__)
def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
Reset button
_set_input_attrs(attrs, type, name, value)
_set_id_attr(attrs, id, name)
convert_boolean_attrs(attrs, ["disabled"])
return HTML.input(**attrs)
reset = _reset
safeid = _make_safe_id_component
def FID(raw_id, path):
Creates a uniqe ID for filenode based on it's hash of path and revision
it's safe to use in urls
:param raw_id:
:param path:
return 'C-%s-%s' % (short_id(raw_id), md5(path).hexdigest()[:12])
def get_token():
"""Return the current authentication token, creating one if one doesn't
already exist.
token_key = "_authentication_token"
from pylons import session
if not token_key in session:
try:
token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
except AttributeError: # Python < 2.4
token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
session[token_key] = token
if hasattr(session, 'save'):
session.save()
return session[token_key]
class _GetError(object):
"""Get error from form_errors, and represent it as span wrapped error
message
:param field_name: field to fetch errors for
:param form_errors: form errors dict
def __call__(self, field_name, form_errors):
tmpl = """<span class="error_msg">%s</span>"""
if form_errors and form_errors.has_key(field_name):
return literal(tmpl % form_errors.get(field_name))
get_error = _GetError()
class _ToolTip(object):
def __call__(self, tooltip_title, trim_at=50):
"""Special function just to wrap our text into nice formatted
autowrapped text
:param tooltip_title:
return escape(tooltip_title)
tooltip = _ToolTip()
class _FilesBreadCrumbs(object):
def __call__(self, repo_name, rev, paths):
if isinstance(paths, str):
paths = safe_unicode(paths)
url_l = [link_to(repo_name, url('files_home',
repo_name=repo_name,
revision=rev, f_path=''))]
paths_l = paths.split('/')
for cnt, p in enumerate(paths_l):
if p != '':
url_l.append(link_to(p,
url('files_home',
revision=rev,
f_path='/'.join(paths_l[:cnt + 1])
)
return literal('/'.join(url_l))
files_breadcrumbs = _FilesBreadCrumbs()
class CodeHtmlFormatter(HtmlFormatter):
"""My code Html Formatter for source codes
def wrap(self, source, outfile):
return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
def _wrap_code(self, source):
for cnt, it in enumerate(source):
i, t = it
t = '<div id="L%s">%s</div>' % (cnt + 1, t)
yield i, t
def _wrap_tablelinenos(self, inner):
dummyoutfile = StringIO.StringIO()
lncount = 0
for t, line in inner:
if t:
lncount += 1
dummyoutfile.write(line)
fl = self.linenostart
mw = len(str(lncount + fl - 1))
sp = self.linenospecial
st = self.linenostep
la = self.lineanchors
aln = self.anchorlinenos
nocls = self.noclasses
if sp:
lines = []
for i in range(fl, fl + lncount):
if i % st == 0:
if i % sp == 0:
if aln:
lines.append('<a href="#%s%d" class="special">%*d</a>' %
(la, i, mw, i))
else:
lines.append('<span class="special">%*d</span>' % (mw, i))
lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
lines.append('%*d' % (mw, i))
lines.append('')
ls = '\n'.join(lines)
# in case you wonder about the seemingly redundant <div> here: since the
# content in the other cell also is wrapped in a div, some browsers in
# some configurations seem to mess up the formatting...
if nocls:
yield 0, ('<table class="%stable">' % self.cssclass +
'<tr><td><div class="linenodiv" '
'style="background-color: #f0f0f0; padding-right: 10px">'
'<pre style="line-height: 125%">' +
ls + '</pre></div></td><td id="hlcode" class="code">')
'<tr><td class="linenos"><div class="linenodiv"><pre>' +
yield 0, dummyoutfile.getvalue()
yield 0, '</td></tr></table>'
def pygmentize(filenode, **kwargs):
"""pygmentize function using pygments
:param filenode:
return literal(code_highlight(filenode.content,
filenode.lexer, CodeHtmlFormatter(**kwargs)))
def pygmentize_annotation(repo_name, filenode, **kwargs):
pygmentize function for annotation
color_dict = {}
def gen_color(n=10000):
"""generator for getting n of evenly distributed colors using
hsv color and golden ratio. It always return same order of colors
:returns: RGB tuple
def hsv_to_rgb(h, s, v):
if s == 0.0:
return v, v, v
i = int(h * 6.0) # XXX assume int() truncates!
f = (h * 6.0) - i
p = v * (1.0 - s)
q = v * (1.0 - s * f)
t = v * (1.0 - s * (1.0 - f))
i = i % 6
if i == 0:
return v, t, p
if i == 1:
return q, v, p
if i == 2:
return p, v, t
if i == 3:
return p, q, v
if i == 4:
return t, p, v
if i == 5:
return v, p, q
golden_ratio = 0.618033988749895
h = 0.22717784590367374
for _ in xrange(n):
h += golden_ratio
h %= 1
HSV_tuple = [h, 0.95, 0.95]
RGB_tuple = hsv_to_rgb(*HSV_tuple)
yield map(lambda x: str(int(x * 256)), RGB_tuple)
cgenerator = gen_color()
def get_color_string(cs):
if cs in color_dict:
col = color_dict[cs]
col = color_dict[cs] = cgenerator.next()
return "color: rgb(%s)! important;" % (', '.join(col))
def url_func(repo_name):
def _url_func(changeset):
author = changeset.author
date = changeset.date
message = tooltip(changeset.message)
tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
" %s<br/><b>Date:</b> %s</b><br/><b>Message:"
"</b> %s<br/></div>")
tooltip_html = tooltip_html % (author, date, message)
lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
short_id(changeset.raw_id))
uri = link_to(
lnk_format,
url('changeset_home', repo_name=repo_name,
revision=changeset.raw_id),
style=get_color_string(changeset.raw_id),
class_='tooltip',
title=tooltip_html
uri += '\n'
return uri
return _url_func
return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
def is_following_repo(repo_name, user_id):
from rhodecode.model.scm import ScmModel
return ScmModel().is_following_repo(repo_name, user_id)
flash = _Flash()
#==============================================================================
# SCM FILTERS available via h.
from rhodecode.lib.vcs.utils import author_name, author_email
from rhodecode.lib.utils2 import credentials_filter, age as _age
from rhodecode.model.db import User
age = lambda x: _age(x)
capitalize = lambda x: x.capitalize()
email = author_email
short_id = lambda x: x[:12]
hide_credentials = lambda x: ''.join(credentials_filter(x))
def is_git(repository):
if hasattr(repository, 'alias'):
_type = repository.alias
elif hasattr(repository, 'repo_type'):
_type = repository.repo_type
_type = repository
return _type == 'git'
def is_hg(repository):
return _type == 'hg'
def email_or_none(author):
_email = email(author)
if _email != '':
return _email
# See if it contains a username we can get an email from
user = User.get_by_username(author_name(author), case_insensitive=True,
cache=True)
if user is not None:
return user.email
# No valid email, not a valid user in the system, none!
return None
def person(author):
# attr to return from fetched user
person_getter = lambda usr: usr.username
# Valid email in the attribute passed, see if they're in the system
user = User.get_by_email(_email, case_insensitive=True, cache=True)
return person_getter(user)
# Maybe it's a username?
_author = author_name(author)
user = User.get_by_username(_author, case_insensitive=True,
# Still nothing? Just pass back the author name then
return _author
def bool2icon(value):
"""Returns True/False values represented as small html image of true/false
icons
:param value: bool value
if value is True:
return HTML.tag('img', src=url("/images/icons/accept.png"),
alt=_('True'))
if value is False:
return HTML.tag('img', src=url("/images/icons/cancel.png"),
alt=_('False'))
return value
def action_parser(user_log, feed=False):
This helper will action_map the specified string action into translated
fancy names with icons and links
:param user_log: user log instance
:param feed: use output for feeds (no html and fancy icons)
action = user_log.action
action_params = ' '
x = action.split(':')
if len(x) > 1:
action, action_params = x
def get_cs_links():
revs_limit = 3 # display this amount always
revs_top_limit = 50 # show upto this amount of changesets hidden
revs_ids = action_params.split(',')
deleted = user_log.repository is None
if deleted:
return ','.join(revs_ids)
repo_name = user_log.repository.repo_name
repo = user_log.repository.scm_instance
message = lambda rev: rev.message
lnk = lambda rev, repo_name: (
link_to('r%s:%s' % (rev.revision, rev.short_id),
revision=rev.raw_id),
title=tooltip(message(rev)), class_='tooltip')
# get only max revs_top_limit of changeset for performance/ui reasons
revs = [
x for x in repo.get_changesets(revs_ids[0],
revs_ids[:revs_top_limit][-1])
]
cs_links = []
cs_links.append(" " + ', '.join(
[lnk(rev, repo_name) for rev in revs[:revs_limit]]
compare_view = (
' <div class="compare_view tooltip" title="%s">'
'<a href="%s">%s</a> </div>' % (
_('Show all combined changesets %s->%s') % (
revs_ids[0], revs_ids[-1]
),
revision='%s...%s' % (revs_ids[0], revs_ids[-1])
_('compare view')
# if we have exactly one more than normally displayed
# just display it, takes less space than displaying
# "and 1 more revisions"
if len(revs_ids) == revs_limit + 1:
rev = revs[revs_limit]
cs_links.append(", " + lnk(rev, repo_name))
# hidden-by-default ones
if len(revs_ids) > revs_limit + 1:
uniq_id = revs_ids[0]
html_tmpl = (
'<span> %s <a class="show_more" id="_%s" '
'href="#more">%s</a> %s</span>'
if not feed:
cs_links.append(html_tmpl % (
_('and'),
uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
_('revisions')
html_tmpl = '<span id="%s" style="display:none">, %s </span>'
html_tmpl = '<span id="%s"> %s </span>'
morelinks = ', '.join(
[lnk(rev, repo_name) for rev in revs[revs_limit:]]
if len(revs_ids) > revs_top_limit:
morelinks += ', ...'
cs_links.append(html_tmpl % (uniq_id, morelinks))
if len(revs) > 1:
cs_links.append(compare_view)
return ''.join(cs_links)
def get_fork_name():
repo_name = action_params
return _('fork name ') + str(link_to(action_params, url('summary_home',
repo_name=repo_name,)))
action_map = {'user_deleted_repo': (_('[deleted] repository'), None),
'user_created_repo': (_('[created] repository'), None),
'user_created_fork': (_('[created] repository as fork'), None),
'user_forked_repo': (_('[forked] repository'), get_fork_name),
'user_updated_repo': (_('[updated] repository'), None),
'admin_deleted_repo': (_('[delete] repository'), None),
'admin_created_repo': (_('[created] repository'), None),
'admin_forked_repo': (_('[forked] repository'), None),
'admin_updated_repo': (_('[updated] repository'), None),
'push': (_('[pushed] into'), get_cs_links),
'push_local': (_('[committed via RhodeCode] into'), get_cs_links),
'push_remote': (_('[pulled from remote] into'), get_cs_links),
'pull': (_('[pulled] from'), None),
'started_following_repo': (_('[started following] repository'), None),
'stopped_following_repo': (_('[stopped following] repository'), None),
}
action_str = action_map.get(action, action)
if feed:
action = action_str[0].replace('[', '').replace(']', '')
action = action_str[0]\
.replace('[', '<span class="journal_highlight">')\
.replace(']', '</span>')
action_params_func = lambda: ""
if callable(action_str[1]):
action_params_func = action_str[1]
return [literal(action), action_params_func]
def action_parser_icon(user_log):
action_params = None
tmpl = """<img src="%s%s" alt="%s"/>"""
map = {'user_deleted_repo':'database_delete.png',
'user_created_repo':'database_add.png',
'user_created_fork':'arrow_divide.png',
'user_forked_repo':'arrow_divide.png',
'user_updated_repo':'database_edit.png',
'admin_deleted_repo':'database_delete.png',
'admin_created_repo':'database_add.png',
'admin_forked_repo':'arrow_divide.png',
'admin_updated_repo':'database_edit.png',
'push':'script_add.png',
'push_local':'script_edit.png',
'push_remote':'connect.png',
'pull':'down_16.png',
'started_following_repo':'heart_add.png',
'stopped_following_repo':'heart_delete.png',
return literal(tmpl % ((url('/images/icons/')),
map.get(action, action), action))
# PERMS
from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
HasRepoPermissionAny, HasRepoPermissionAll
# GRAVATAR URL
def gravatar_url(email_address, size=30):
if (not str2bool(config['app_conf'].get('use_gravatar')) or
not email_address or email_address == 'anonymous@rhodecode.org'):
f = lambda a, l: min(l, key=lambda x: abs(x - a))
return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
default = 'identicon'
baseurl_nossl = "http://www.gravatar.com/avatar/"
baseurl_ssl = "https://secure.gravatar.com/avatar/"
baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
if isinstance(email_address, unicode):
#hashlib crashes on unicode items
email_address = safe_str(email_address)
# construct the url
gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
return gravatar_url
# REPO PAGER, PAGER FOR REPOSITORY
class RepoPage(Page):
def __init__(self, collection, page=1, items_per_page=20,
item_count=None, url=None, **kwargs):
"""Create a "RepoPage" instance. special pager for paging
repository
self._url_generator = url
# Safe the kwargs class-wide so they can be used in the pager() method
self.kwargs = kwargs
# Save a reference to the collection
self.original_collection = collection
self.collection = collection
# The self.page is the number of the current page.
# The first page has the number 1!
self.page = int(page) # make it int() if we get it as a string
except (ValueError, TypeError):
self.page = 1
self.items_per_page = items_per_page
# Unless the user tells us how many items the collections has
# we calculate that ourselves.
if item_count is not None:
self.item_count = item_count
self.item_count = len(self.collection)
# Compute the number of the first and last available page
if self.item_count > 0:
self.first_page = 1
self.page_count = int(math.ceil(float(self.item_count) /
self.items_per_page))
self.last_page = self.first_page + self.page_count - 1
# Make sure that the requested page number is the range of
# valid pages
if self.page > self.last_page:
self.page = self.last_page
elif self.page < self.first_page:
self.page = self.first_page
# Note: the number of items on this page can be less than
# items_per_page if the last page is not full
self.first_item = max(0, (self.item_count) - (self.page *
items_per_page))
self.last_item = ((self.item_count - 1) - items_per_page *
(self.page - 1))
self.items = list(self.collection[self.first_item:self.last_item + 1])
# Links to previous and next page
if self.page > self.first_page:
self.previous_page = self.page - 1
self.previous_page = None
if self.page < self.last_page:
self.next_page = self.page + 1
self.next_page = None
# No items available
self.first_page = None
self.page_count = 0
self.last_page = None
self.first_item = None
self.last_item = None
self.items = []
# This is a subclass of the 'list' type. Initialise the list now.
list.__init__(self, reversed(self.items))
def changed_tooltip(nodes):
Generates a html string for changed nodes in changeset page.
It limits the output to 30 entries
:param nodes: LazyNodesGenerator
if nodes:
pref = ': <br/> '
suf = ''
if len(nodes) > 30:
suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
return literal(pref + '<br/> '.join([safe_unicode(x.path)
for x in nodes[:30]]) + suf)
return ': ' + _('No Files')
def repo_link(groups_and_repos):
Makes a breadcrumbs link to repo within a group
joins » on each group to create a fancy link
ex::
group >> subgroup >> repo
:param groups_and_repos:
groups, repo_name = groups_and_repos
if not groups:
return repo_name
def make_link(group):
return link_to(group.name, url('repos_group_home',
group_name=group.group_name))
return literal(' » '.join(map(make_link, groups)) + \
" » " + repo_name)
def fancy_file_stats(stats):
Displays a fancy two colored bar for number of added/deleted
lines of code on file
:param stats: two element list of added/deleted lines of code
a, d, t = stats[0], stats[1], stats[0] + stats[1]
width = 100
unit = float(width) / (t or 1)
# needs > 9% of width to be visible or 0 to be hidden
a_p = max(9, unit * a) if a > 0 else 0
d_p = max(9, unit * d) if d > 0 else 0
p_sum = a_p + d_p
if p_sum > width:
#adjust the percentage to be == 100% since we adjusted to 9
if a_p > d_p:
a_p = a_p - (p_sum - width)
d_p = d_p - (p_sum - width)
a_v = a if a > 0 else ''
d_v = d if d > 0 else ''
def cgen(l_type):
mapping = {'tr': 'top-right-rounded-corner',
'tl': 'top-left-rounded-corner',
'br': 'bottom-right-rounded-corner',
'bl': 'bottom-left-rounded-corner'}
mapping = {'tr': 'top-right-rounded-corner-mid',
'tl': 'top-left-rounded-corner-mid',
'br': 'bottom-right-rounded-corner-mid',
'bl': 'bottom-left-rounded-corner-mid'}
map_getter = lambda x: mapping[x]
if l_type == 'a' and d_v:
#case when added and deleted are present
return ' '.join(map(map_getter, ['tl', 'bl']))
if l_type == 'a' and not d_v:
return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
if l_type == 'd' and a_v:
return ' '.join(map(map_getter, ['tr', 'br']))
if l_type == 'd' and not a_v:
d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
cgen('a'), a_p, a_v
d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
cgen('d'), d_p, d_v
return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
def urlify_text(text_):
import re
url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
'''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
def url_func(match_obj):
url_full = match_obj.groups()[0]
return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
return literal(url_pat.sub(url_func, text_))
def urlify_changesets(text_, repository):
Extract revision ids from changeset and make link from them
:param text_:
:param repository:
URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
rev = match_obj.groups()[0]
pref = ''
if match_obj.group().startswith(' '):
pref = ' '
tmpl = (
'%(pref)s<a class="%(cls)s" href="%(url)s">'
'%(rev)s'
'</a>'
return tmpl % {
'pref': pref,
'cls': 'revision-link',
'url': url('changeset_home', repo_name=repository, revision=rev),
'rev': rev,
newtext = URL_PAT.sub(url_func, text_)
return newtext
def urlify_commit(text_, repository=None, link_=None):
Parses given text message and makes proper links.
issues are linked to given issue-server, and rest is a changeset link
if link_ is given, in other case it's a plain text
:param link_: changeset link
import traceback
def escaper(string):
return string.replace('<', '<').replace('>', '>')
def linkify_others(t, l):
urls = re.compile(r'(\<a.*?\<\/a\>)',)
links = []
for e in urls.split(t):
if not urls.match(e):
links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
links.append(e)
return ''.join(links)
# urlify changesets - extrac revisions and make link out of them
text_ = urlify_changesets(escaper(text_), repository)
conf = config['app_conf']
URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
if URL_PAT:
ISSUE_SERVER_LNK = conf.get('issue_server_link')
ISSUE_PREFIX = conf.get('issue_prefix')
issue_id = ''.join(match_obj.groups())
'%(issue-prefix)s%(id-repr)s'
url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
if repository:
url = url.replace('{repo}', repository)
'cls': 'issue-tracker-link',
'url': url,
'id-repr': issue_id,
'issue-prefix': ISSUE_PREFIX,
'serv': ISSUE_SERVER_LNK,
if link_:
# wrap not links into final link => link_
newtext = linkify_others(newtext, link_)
return literal(newtext)
except:
log.error(traceback.format_exc())
pass
return text_
def rst(source):
return literal('<div class="rst-block">%s</div>' %
MarkupRenderer.rst(source))
def rst_w_mentions(source):
Wrapped rst renderer with @mention highlighting
:param source:
MarkupRenderer.rst_with_mentions(source))
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td
{
border: 0;
outline: 0;
font-size: 100%;
vertical-align: baseline;
background: transparent;
margin: 0;
padding: 0;
body {
line-height: 1;
height: 100%;
background: url("../images/background.png") repeat scroll 0 0 #B0B0B0;
font-family: Lucida Grande, Verdana, Lucida Sans Regular,
Lucida Sans Unicode, Arial, sans-serif; font-size : 12px;
color: #000;
font-size: 12px;
ol,ul {
list-style: none;
blockquote,q {
quotes: none;
blockquote:before,blockquote:after,q:before,q:after {
content: none;
:focus {
del {
text-decoration: line-through;
table {
border-collapse: collapse;
border-spacing: 0;
html {
a {
color: #003367;
text-decoration: none;
cursor: pointer;
a:hover {
color: #316293;
text-decoration: underline;
h1,h2,h3,h4,h5,h6 {
color: #292929;
font-weight: 700;
h1 {
font-size: 22px;
h2 {
font-size: 20px;
h3 {
font-size: 18px;
h4 {
font-size: 16px;
h5 {
font-size: 14px;
h6 {
font-size: 11px;
ul.circle {
list-style-type: circle;
ul.disc {
list-style-type: disc;
ul.square {
list-style-type: square;
ol.lower-roman {
list-style-type: lower-roman;
ol.upper-roman {
list-style-type: upper-roman;
ol.lower-alpha {
list-style-type: lower-alpha;
ol.upper-alpha {
list-style-type: upper-alpha;
ol.decimal {
list-style-type: decimal;
div.color {
clear: both;
overflow: hidden;
position: absolute;
background: #FFF;
margin: 7px 0 0 60px;
padding: 1px 1px 1px 0;
div.color a {
width: 15px;
height: 15px;
display: block;
float: left;
margin: 0 0 0 1px;
div.options {
margin: 7px 0 0 162px;
div.options a {
height: 1%;
padding: 3px 8px;
.top-left-rounded-corner {
-webkit-border-top-left-radius: 8px;
-khtml-border-radius-topleft: 8px;
-moz-border-radius-topleft: 8px;
border-top-left-radius: 8px;
.top-right-rounded-corner {
-webkit-border-top-right-radius: 8px;
-khtml-border-radius-topright: 8px;
-moz-border-radius-topright: 8px;
border-top-right-radius: 8px;
.bottom-left-rounded-corner {
-webkit-border-bottom-left-radius: 8px;
-khtml-border-radius-bottomleft: 8px;
-moz-border-radius-bottomleft: 8px;
border-bottom-left-radius: 8px;
.bottom-right-rounded-corner {
-webkit-border-bottom-right-radius: 8px;
-khtml-border-radius-bottomright: 8px;
-moz-border-radius-bottomright: 8px;
border-bottom-right-radius: 8px;
.top-left-rounded-corner-mid {
-webkit-border-top-left-radius: 4px;
-khtml-border-radius-topleft: 4px;
-moz-border-radius-topleft: 4px;
border-top-left-radius: 4px;
.top-right-rounded-corner-mid {
-webkit-border-top-right-radius: 4px;
-khtml-border-radius-topright: 4px;
-moz-border-radius-topright: 4px;
border-top-right-radius: 4px;
.bottom-left-rounded-corner-mid {
-webkit-border-bottom-left-radius: 4px;
-khtml-border-radius-bottomleft: 4px;
-moz-border-radius-bottomleft: 4px;
border-bottom-left-radius: 4px;
.bottom-right-rounded-corner-mid {
-webkit-border-bottom-right-radius: 4px;
-khtml-border-radius-bottomright: 4px;
-moz-border-radius-bottomright: 4px;
border-bottom-right-radius: 4px;
.help-block {
color: #999999;
margin-bottom: 0;
margin-top: 5px;
#header {
padding: 0 10px;
#header ul#logged-user {
margin-bottom: 5px !important;
-webkit-border-radius: 0px 0px 8px 8px;
-khtml-border-radius: 0px 0px 8px 8px;
-moz-border-radius: 0px 0px 8px 8px;
border-radius: 0px 0px 8px 8px;
height: 37px;
background-color: #eedc94;
background-repeat: repeat-x;
background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),
to(#eedc94) );
background-image: -moz-linear-gradient(top, #003b76, #00376e);
background-image: -ms-linear-gradient(top, #003b76, #00376e);
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76), color-stop(100%, #00376e) );
background-image: -webkit-linear-gradient(top, #003b76, #00376e);
background-image: -o-linear-gradient(top, #003b76, #00376e);
background-image: linear-gradient(top, #003b76, #00376e);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',
endColorstr='#00376e', GradientType=0 );
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
#header ul#logged-user li {
margin: 8px 0 0;
padding: 4px 12px;
border-left: 1px solid #316293;
#header ul#logged-user li.first {
border-left: none;
margin: 4px;
#header ul#logged-user li.first div.gravatar {
margin-top: -2px;
#header ul#logged-user li.first div.account {
padding-top: 4px;
#header ul#logged-user li.last {
border-right: none;
#header ul#logged-user li a {
color: #fff;
#header ul#logged-user li a:hover {
#header ul#logged-user li.highlight a {
#header ul#logged-user li.highlight a:hover {
color: #FFF;
#header #header-inner {
min-height: 44px;
position: relative;
background-image: -khtml-gradient(linear, left top, left bottom, from(#fceec1),to(#eedc94) );
background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #003b76),color-stop(100%, #00376e) );
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#003b76',endColorstr='#00376e', GradientType=0 );
-webkit-border-radius: 4px 4px 4px 4px;
-khtml-border-radius: 4px 4px 4px 4px;
-moz-border-radius: 4px 4px 4px 4px;
border-radius: 4px 4px 4px 4px;
#header #header-inner.hover{
position: fixed !important;
width: 100% !important;
margin-left: -10px !important;
z-index: 10000;
-webkit-border-radius: 0px 0px 0px 0px;
-khtml-border-radius: 0px 0px 0px 0px;
-moz-border-radius: 0px 0px 0px 0px;
border-radius: 0px 0px 0px 0px;
#header #header-inner #home a {
height: 40px;
width: 46px;
background: url("../images/button_home.png");
background-position: 0 0;
#header #header-inner #home a:hover {
background-position: 0 -40px;
#header #header-inner #logo {
#header #header-inner #logo h1 {
margin: 12px 0 0 13px;
#header #header-inner #logo a {
#header #header-inner #logo a:hover {
color: #bfe3ff;
#header #header-inner #quick,#header #header-inner #quick ul {
float: right;
list-style-type: none;
list-style-position: outside;
margin: 8px 8px 0 0;
#header #header-inner #quick li {
margin: 0 5px 0 0;
#header #header-inner #quick li a.menu_link {
top: 0;
left: 0;
background: #369;
#header #header-inner #quick li span.short {
padding: 9px 6px 8px 6px;
#header #header-inner #quick li span {
right: 0;
border-left: 1px solid #3f6f9f;
padding: 10px 12px 8px 10px;
#header #header-inner #quick li span.normal {
border: none;
padding: 10px 12px 8px;
#header #header-inner #quick li span.icon {
border-right: 1px solid #2e5c89;
padding: 8px 6px 4px;
#header #header-inner #quick li span.icon_short {
#header #header-inner #quick li span.icon img,#header #header-inner #quick li span.icon_short img
margin: 0px -2px 0px 0px;
#header #header-inner #quick li a:hover {
background: #4e4e4e no-repeat top left;
#header #header-inner #quick li a:hover span {
border-left: 1px solid #545454;
#header #header-inner #quick li a:hover span.icon,#header #header-inner #quick li a:hover span.icon_short
border-right: 1px solid #464646;
#header #header-inner #quick ul {
top: 29px;
min-width: 200px;
display: none;
border: 1px solid #666;
border-top: 1px solid #003367;
z-index: 100;
margin: 0px 0px 0px 0px;
#header #header-inner #quick ul.repo_switcher {
max-height: 275px;
overflow-x: hidden;
overflow-y: auto;
#header #header-inner #quick ul.repo_switcher li.qfilter_rs {
float: none;
border-bottom: 2px solid #003367;
#header #header-inner #quick .repo_switcher_type {
top: 9px;
#header #header-inner #quick li ul li {
border-bottom: 1px solid #ddd;
#header #header-inner #quick li ul li a {
width: 182px;
height: auto;
font-weight: 400;
padding: 7px 9px;
#header #header-inner #quick li ul li a:hover {
#header #header-inner #quick ul ul {
top: auto;
#header #header-inner #quick li ul ul {
right: 200px;
overflow: auto;
white-space: normal;
#header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover
background: url("../images/icons/book.png") no-repeat scroll 4px 9px
#FFF;
width: 167px;
padding: 12px 9px 7px 24px;
#header #header-inner #quick li ul li a.private_repo,#header #header-inner #quick li ul li a.private_repo:hover
background: url("../images/icons/lock.png") no-repeat scroll 4px 9px
min-width: 167px;
#header #header-inner #quick li ul li a.public_repo,#header #header-inner #quick li ul li a.public_repo:hover
background: url("../images/icons/lock_open.png") no-repeat scroll 4px
9px #FFF;
#header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover
background: url("../images/icons/hgicon.png") no-repeat scroll 4px 9px
margin: 0 0 0 14px;
#header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover
background: url("../images/icons/giticon.png") no-repeat scroll 4px 9px
#header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover
background: url("../images/icons/database_edit.png") no-repeat scroll
4px 9px #FFF;
#header #header-inner #quick li ul li a.repos_groups,#header #header-inner #quick li ul li a.repos_groups:hover
background: url("../images/icons/database_link.png") no-repeat scroll
#header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover
background: #FFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.groups,#header #header-inner #quick li ul li a.groups:hover
background: #FFF url("../images/icons/group_edit.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.settings,#header #header-inner #quick li ul li a.settings:hover
background: #FFF url("../images/icons/cog.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.permissions,#header #header-inner #quick li ul li a.permissions:hover
background: #FFF url("../images/icons/key.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover
background: #FFF url("../images/icons/server_key.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover
background: #FFF url("../images/icons/arrow_divide.png") no-repeat 4px
9px;
#header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover
background: #FFF url("../images/icons/search_16.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.delete,#header #header-inner #quick li ul li a.delete:hover
background: #FFF url("../images/icons/delete.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover
background: #FFF url("../images/icons/arrow_branch.png") no-repeat 4px
#header #header-inner #quick li ul li a.tags,
#header #header-inner #quick li ul li a.tags:hover{
background: #FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.bookmarks,
#header #header-inner #quick li ul li a.bookmarks:hover{
background: #FFF url("../images/icons/tag_green.png") no-repeat 4px 9px;
#header #header-inner #quick li ul li a.admin,
#header #header-inner #quick li ul li a.admin:hover{
background: #FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
.groups_breadcrumbs a {
.groups_breadcrumbs a:hover {
td.quick_repo_menu {
background: #FFF url("../images/vertical-indicator.png") 8px 50% no-repeat !important;
width: 8px;
border: 1px solid transparent;
td.quick_repo_menu.active {
background: url("../images/dt-arrow-dn.png") no-repeat scroll 5px 50% #FFFFFF !important;
border: 1px solid #003367;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
td.quick_repo_menu .menu_items {
margin-top: 10px;
margin-left:-6px;
width: 150px;
background-color: #FFF;
background: none repeat scroll 0 0 #FFFFFF;
border-color: #003367 #666666 #666666;
border-right: 1px solid #666666;
border-style: solid;
border-width: 1px;
box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
border-top-style: none;
td.quick_repo_menu .menu_items li {
padding: 0 !important;
td.quick_repo_menu .menu_items a {
padding: 4px 12px 4px 8px;
td.quick_repo_menu .menu_items a:hover {
background-color: #EEE;
td.quick_repo_menu .menu_items .icon img {
margin-bottom: -2px;
td.quick_repo_menu .menu_items.hidden {
.yui-dt-first th {
text-align: left;
/*
Copyright (c) 2011, Yahoo! Inc. All rights reserved.
Code licensed under the BSD License:
http://developer.yahoo.com/yui/license.html
version: 2.9.0
*/
.yui-skin-sam .yui-dt-mask {
z-index: 9500;
.yui-dt-tmp {
left: -9000px;
.yui-dt-scrollable .yui-dt-bd { overflow: auto }
.yui-dt-scrollable .yui-dt-hd {
.yui-dt-scrollable .yui-dt-bd thead tr,
.yui-dt-scrollable .yui-dt-bd thead th {
left: -1500px;
.yui-dt-scrollable tbody { -moz-outline: 0 }
.yui-skin-sam thead .yui-dt-sortable { cursor: pointer }
.yui-skin-sam thead .yui-dt-draggable { cursor: move }
.yui-dt-coltarget {
z-index: 999;
.yui-dt-hd { zoom: 1 }
th.yui-dt-resizeable .yui-dt-resizerliner { position: relative }
.yui-dt-resizer {
bottom: 0;
cursor: e-resize;
cursor: col-resize;
background-color: #CCC;
opacity: 0;
filter: alpha(opacity=0);
.yui-dt-resizerproxy {
visibility: hidden;
z-index: 9000;
th.yui-dt-hidden .yui-dt-liner,
td.yui-dt-hidden .yui-dt-liner,
th.yui-dt-hidden .yui-dt-resizer { display: none }
.yui-dt-editor,
.yui-dt-editor-shim {
.yui-skin-sam .yui-dt table {
font-family: arial;
font-size: inherit;
border-collapse: separate;
*border-collapse: collapse;
border: 1px solid #7f7f7f;
.yui-skin-sam .yui-dt thead { border-spacing: 0 }
.yui-skin-sam .yui-dt caption {
font-size: 85%;
font-weight: normal;
font-style: italic;
padding: 1em 0;
text-align: center;
.yui-skin-sam .yui-dt th { background: #d8d8da url(../images/sprite.png) repeat-x 0 0 }
.yui-skin-sam .yui-dt th,
.yui-skin-sam .yui-dt th a {
vertical-align: bottom;
.yui-skin-sam .yui-dt th {
border-right: 1px solid #cbcbcb;
.yui-skin-sam .yui-dt tr.yui-dt-first td { border-top: 1px solid #7f7f7f }
.yui-skin-sam .yui-dt th .yui-dt-liner { white-space: nowrap }
.yui-skin-sam .yui-dt-liner {
.yui-skin-sam .yui-dt-coltarget {
width: 5px;
background-color: red;
.yui-skin-sam .yui-dt td {
.yui-skin-sam .yui-dt-list td { border-right: 0 }
.yui-skin-sam .yui-dt-resizer { width: 6px }
background-color: #000;
opacity: .25;
filter: alpha(opacity=25);
.yui-skin-sam .yui-dt-message { background-color: #FFF }
.yui-skin-sam .yui-dt-scrollable table { border: 0 }
.yui-skin-sam .yui-dt-scrollable .yui-dt-hd {
border-left: 1px solid #7f7f7f;
border-top: 1px solid #7f7f7f;
border-right: 1px solid #7f7f7f;
.yui-skin-sam .yui-dt-scrollable .yui-dt-bd {
border-bottom: 1px solid #7f7f7f;
.yui-skin-sam .yui-dt-scrollable .yui-dt-data tr.yui-dt-last td { border-bottom: 1px solid #7f7f7f }
.yui-skin-sam th.yui-dt-asc,
.yui-skin-sam th.yui-dt-desc { background: url(../images/sprite.png) repeat-x 0 -100px }
.yui-skin-sam th.yui-dt-sortable .yui-dt-label { margin-right: 10px }
.yui-skin-sam th.yui-dt-asc .yui-dt-liner { background: url(../images/dt-arrow-up.png) no-repeat right }
.yui-skin-sam th.yui-dt-desc .yui-dt-liner { background: url(../images/dt-arrow-dn.png) no-repeat right }
tbody .yui-dt-editable { cursor: pointer }
.yui-dt-editor {
background-color: #f2f2f2;
border: 1px solid #808080;
padding: 6px;
.yui-dt-editor label {
padding-left: 4px;
padding-right: 6px;
.yui-dt-editor .yui-dt-button {
padding-top: 6px;
text-align: right;
.yui-dt-editor .yui-dt-button button {
background: url(../images/sprite.png) repeat-x 0 0;
border: 1px solid #999;
width: 4em;
height: 1.8em;
margin-left: 6px;
.yui-dt-editor .yui-dt-button button.yui-dt-default {
background: url(../images/sprite.png) repeat-x 0 -1400px;
background-color: #5584e0;
border: 1px solid #304369;
.yui-dt-editor .yui-dt-button button:hover {
background: url(../images/sprite.png) repeat-x 0 -1300px;
.yui-dt-editor .yui-dt-button button:active {
background: url(../images/sprite.png) repeat-x 0 -1700px;
.yui-skin-sam tr.yui-dt-even { background-color: #FFF }
.yui-skin-sam tr.yui-dt-odd { background-color: #edf5ff }
.yui-skin-sam tr.yui-dt-even td.yui-dt-asc,
.yui-skin-sam tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
.yui-skin-sam tr.yui-dt-odd td.yui-dt-asc,
.yui-skin-sam tr.yui-dt-odd td.yui-dt-desc { background-color: #dbeaff }
.yui-skin-sam .yui-dt-list tr.yui-dt-even { background-color: #FFF }
.yui-skin-sam .yui-dt-list tr.yui-dt-odd { background-color: #FFF }
.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-asc,
.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-desc { background-color: #edf5ff }
.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-asc,
.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-desc { background-color: #edf5ff }
.yui-skin-sam th.yui-dt-highlighted,
.yui-skin-sam th.yui-dt-highlighted a { background-color: #b2d2ff }
.yui-skin-sam tr.yui-dt-highlighted,
.yui-skin-sam tr.yui-dt-highlighted td.yui-dt-asc,
.yui-skin-sam tr.yui-dt-highlighted td.yui-dt-desc,
.yui-skin-sam tr.yui-dt-even td.yui-dt-highlighted,
.yui-skin-sam tr.yui-dt-odd td.yui-dt-highlighted {
background-color: #b2d2ff;
.yui-skin-sam .yui-dt-list th.yui-dt-highlighted,
.yui-skin-sam .yui-dt-list th.yui-dt-highlighted a { background-color: #b2d2ff }
.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted,
.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-asc,
.yui-skin-sam .yui-dt-list tr.yui-dt-highlighted td.yui-dt-desc,
.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-highlighted,
.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-highlighted {
.yui-skin-sam th.yui-dt-selected,
.yui-skin-sam th.yui-dt-selected a { background-color: #446cd7 }
.yui-skin-sam tr.yui-dt-selected td,
.yui-skin-sam tr.yui-dt-selected td.yui-dt-asc,
.yui-skin-sam tr.yui-dt-selected td.yui-dt-desc {
background-color: #426fd9;
.yui-skin-sam tr.yui-dt-even td.yui-dt-selected,
.yui-skin-sam tr.yui-dt-odd td.yui-dt-selected {
background-color: #446cd7;
.yui-skin-sam .yui-dt-list th.yui-dt-selected,
.yui-skin-sam .yui-dt-list th.yui-dt-selected a { background-color: #446cd7 }
.yui-skin-sam .yui-dt-list tr.yui-dt-selected td,
.yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-asc,
.yui-skin-sam .yui-dt-list tr.yui-dt-selected td.yui-dt-desc {
.yui-skin-sam .yui-dt-list tr.yui-dt-even td.yui-dt-selected,
.yui-skin-sam .yui-dt-list tr.yui-dt-odd td.yui-dt-selected {
.yui-skin-sam .yui-dt-paginator {
margin: 6px 0;
white-space: nowrap;
.yui-skin-sam .yui-dt-paginator .yui-dt-first,
.yui-skin-sam .yui-dt-paginator .yui-dt-last,
.yui-skin-sam .yui-dt-paginator .yui-dt-selected { padding: 2px 6px }
.yui-skin-sam .yui-dt-paginator a.yui-dt-first,
.yui-skin-sam .yui-dt-paginator a.yui-dt-last { text-decoration: none }
.yui-skin-sam .yui-dt-paginator .yui-dt-previous,
.yui-skin-sam .yui-dt-paginator .yui-dt-next { display: none }
Status change: