diff --git a/rhodecode/apps/hovercards/__init__.py b/rhodecode/apps/hovercards/__init__.py --- a/rhodecode/apps/hovercards/__init__.py +++ b/rhodecode/apps/hovercards/__init__.py @@ -26,6 +26,10 @@ def includeme(config): pattern='/_hovercard/user/{user_id}') config.add_route( + name='hovercard_username', + pattern='/_hovercard/username/{username}') + + config.add_route( name='hovercard_user_group', pattern='/_hovercard/user_group/{user_group_id}') diff --git a/rhodecode/apps/hovercards/views.py b/rhodecode/apps/hovercards/views.py --- a/rhodecode/apps/hovercards/views.py +++ b/rhodecode/apps/hovercards/views.py @@ -65,6 +65,19 @@ class HoverCardsView(BaseAppView): @LoginRequired() @view_config( + route_name='hovercard_username', request_method='GET', xhr=True, + renderer='rhodecode:templates/hovercards/hovercard_user.mako') + def hovercard_username(self): + c = self.load_default_context() + username = self.request.matchdict['username'] + c.user = User.get_by_username(username) + if not c.user: + raise HTTPNotFound() + + return self._get_template_context(c) + + @LoginRequired() + @view_config( route_name='hovercard_user_group', request_method='GET', xhr=True, renderer='rhodecode:templates/hovercards/hovercard_user_group.mako') def hovercard_user_group(self): diff --git a/rhodecode/lib/bleach_whitelist.py b/rhodecode/lib/bleach_whitelist.py --- a/rhodecode/lib/bleach_whitelist.py +++ b/rhodecode/lib/bleach_whitelist.py @@ -66,11 +66,12 @@ markdown_tags = [ markdown_attrs = { "*": ["class", "style", "align"], "img": ["src", "alt", "title"], - "a": ["href", "alt", "title", "name"], + "a": ["href", "alt", "title", "name", "data-hovercard-alt", "data-hovercard-url"], "abbr": ["title"], "acronym": ["title"], "pre": ["lang"], - "input": ["type", "disabled", "checked"] + "input": ["type", "disabled", "checked"], + "strong": ["title", "data-hovercard-alt", "data-hovercard-url"], } standard_styles = [ diff --git a/rhodecode/lib/markup_renderer.py b/rhodecode/lib/markup_renderer.py --- a/rhodecode/lib/markup_renderer.py +++ b/rhodecode/lib/markup_renderer.py @@ -47,6 +47,14 @@ log = logging.getLogger(__name__) # default renderer used to generate automated comments DEFAULT_COMMENTS_RENDERER = 'rst' +try: + from lxml.html import fromstring + from lxml.html import tostring +except ImportError: + log.exception('Failed to import lxml') + fromstring = None + tostring = None + class CustomHTMLTranslator(writers.html4css1.HTMLTranslator): """ @@ -81,11 +89,7 @@ def relative_links(html_source, server_p if not html_source: return html_source - try: - from lxml.html import fromstring - from lxml.html import tostring - except ImportError: - log.exception('Failed to import lxml') + if not fromstring and tostring: return html_source try: @@ -210,6 +214,8 @@ class MarkupRenderer(object): URL_PAT = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]' r'|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)') + MENTION_PAT = re.compile(MENTIONS_REGEX) + extensions = ['markdown.extensions.codehilite', 'markdown.extensions.extra', 'markdown.extensions.def_list', 'markdown.extensions.sane_lists'] @@ -348,6 +354,26 @@ class MarkupRenderer(object): return cls.URL_PAT.sub(url_func, text) @classmethod + def convert_mentions(cls, text, mode): + mention_pat = cls.MENTION_PAT + + def wrapp(match_obj): + uname = match_obj.groups()[0] + hovercard_url = "pyroutes.url('hovercard_username', {'username': '%s'});" % uname + + if mode == 'markdown': + tmpl = '@{uname}' + elif mode == 'rst': + tmpl = ' **@{uname}** ' + else: + raise ValueError('mode must be rst or markdown') + + return tmpl.format(**{'uname': uname, + 'hovercard_url': hovercard_url}) + + return mention_pat.sub(wrapp, text).strip() + + @classmethod def plain(cls, source, universal_newline=True, leading_newline=True): source = safe_unicode(source) if universal_newline: @@ -378,12 +404,7 @@ class MarkupRenderer(object): cls.extensions, cls.output_format) if mentions: - mention_pat = re.compile(MENTIONS_REGEX) - - def wrapp(match_obj): - uname = match_obj.groups()[0] - return ' **@%(uname)s** ' % {'uname': uname} - mention_hl = mention_pat.sub(wrapp, source).strip() + mention_hl = cls.convert_mentions(source, mode='markdown') # we extracted mentions render with this using Mentions false return cls.markdown(mention_hl, safe=safe, flavored=flavored, mentions=False) @@ -409,12 +430,7 @@ class MarkupRenderer(object): @classmethod def rst(cls, source, safe=True, mentions=False, clean_html=False): if mentions: - mention_pat = re.compile(MENTIONS_REGEX) - - def wrapp(match_obj): - uname = match_obj.groups()[0] - return ' **@%(uname)s** ' % {'uname': uname} - mention_hl = mention_pat.sub(wrapp, source).strip() + mention_hl = cls.convert_mentions(source, mode='rst') # we extracted mentions render with this using Mentions false return cls.rst(mention_hl, safe=safe, mentions=False) @@ -443,7 +459,7 @@ class MarkupRenderer(object): except Exception: log.exception('Error when rendering RST') if safe: - log.debug('Fallbacking to render in plain mode') + log.debug('Fallback to render in plain mode') return cls.plain(source) else: raise diff --git a/rhodecode/public/css/main.less b/rhodecode/public/css/main.less --- a/rhodecode/public/css/main.less +++ b/rhodecode/public/css/main.less @@ -87,6 +87,10 @@ body { border-left: @border-thickness solid @border-default-color; } +.cursor-pointer { + cursor: pointer; +} + input + .action-link, .action-link.first{ border-left: none; } diff --git a/rhodecode/public/js/rhodecode/routes.js b/rhodecode/public/js/rhodecode/routes.js --- a/rhodecode/public/js/rhodecode/routes.js +++ b/rhodecode/public/js/rhodecode/routes.js @@ -31,6 +31,7 @@ function registerRCRoutes() { pyroutes.register('repo_integrations_create', '/%(repo_name)s/settings/integrations/%(integration)s/new', ['repo_name', 'integration']); pyroutes.register('repo_integrations_edit', '/%(repo_name)s/settings/integrations/%(integration)s/%(integration_id)s', ['repo_name', 'integration', 'integration_id']); pyroutes.register('hovercard_user', '/_hovercard/user/%(user_id)s', ['user_id']); + pyroutes.register('hovercard_username', '/_hovercard/username/%(username)s', ['username']); pyroutes.register('hovercard_user_group', '/_hovercard/user_group/%(user_group_id)s', ['user_group_id']); pyroutes.register('hovercard_pull_request', '/_hovercard/pull_request/%(pull_request_id)s', ['pull_request_id']); pyroutes.register('hovercard_repo_commit', '/_hovercard/commit/%(repo_name)s/%(commit_id)s', ['repo_name', 'commit_id']); diff --git a/rhodecode/public/js/src/rhodecode.js b/rhodecode/public/js/src/rhodecode.js --- a/rhodecode/public/js/src/rhodecode.js +++ b/rhodecode/public/js/src/rhodecode.js @@ -299,6 +299,10 @@ var tooltipActivate = function () { var altHovercard =$origin.data('hovercardAlt'); if (hovercardUrl !== undefined && hovercardUrl !== "") { + if (hovercardUrl.substr(0,12) === 'pyroutes.url'){ + hovercardUrl = eval(hovercardUrl) + } + var loaded = loadHoverCard(hovercardUrl, altHovercard, function (data) { instance.content(data); })