Changeset - 025f3333c769
[Not reviewed]
beta
0 5 0
Marcin Kuzminski - 14 years ago 2011-12-08 02:36:13
marcin@python-works.com
@mention highlighting
5 files changed with 22 insertions and 3 deletions:
0 comments (0 inline, 0 general)
rhodecode/lib/helpers.py
Show inline comments
 
@@ -624,96 +624,105 @@ def changed_tooltip(nodes):
 

	
 

	
 
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
 
    else:
 
        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)
 
        else:
 
            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'}
 
        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:
 
            return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
 

	
 

	
 

	
 
    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 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:
 
    """
 
    return literal('<div class="rst-block">%s</div>' % 
 
                   MarkupRenderer.rst_with_mentions(source))    
rhodecode/lib/markup_renderer.py
Show inline comments
 
@@ -34,96 +34,106 @@ log = logging.getLogger(__name__)
 
class MarkupRenderer(object):
 
    RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES = ['include', 'meta', 'raw']
 
    
 
    MARKDOWN_PAT = re.compile(r'md|mkdn?|mdown|markdown',re.IGNORECASE)
 
    RST_PAT = re.compile(r're?st',re.IGNORECASE)
 
    PLAIN_PAT = re.compile(r'readme',re.IGNORECASE)
 
    
 
    def __detect_renderer(self, source, filename=None):
 
        """
 
        runs detection of what renderer should be used for generating html
 
        from a markup language
 
        
 
        filename can be also explicitly a renderer name
 
        
 
        :param source:
 
        :param filename:
 
        """
 

	
 
        if MarkupRenderer.MARKDOWN_PAT.findall(filename):
 
            detected_renderer = 'markdown'
 
        elif MarkupRenderer.RST_PAT.findall(filename):
 
            detected_renderer = 'rst'
 
        elif MarkupRenderer.PLAIN_PAT.findall(filename):
 
            detected_renderer = 'rst'
 
        else:
 
            detected_renderer = 'plain'
 

	
 
        return getattr(MarkupRenderer, detected_renderer)
 

	
 

	
 
    def render(self, source, filename=None):
 
        """
 
        Renders a given filename using detected renderer
 
        it detects renderers based on file extension or mimetype.
 
        At last it will just do a simple html replacing new lines with <br/>
 
        
 
        :param file_name:
 
        :param source:
 
        """
 

	
 
        renderer = self.__detect_renderer(source, filename)
 
        readme_data = renderer(source)
 
        return readme_data
 

	
 
    @classmethod
 
    def plain(cls, source):
 
        source = safe_unicode(source)
 
        def urlify_text(text):
 
            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 url_pat.sub(url_func, text)
 

	
 
        source = urlify_text(source)
 
        return '<br />' + source.replace("\n", '<br />')
 

	
 

	
 
    @classmethod
 
    def markdown(cls, source):
 
        source = safe_unicode(source)
 
        try:
 
            import markdown as __markdown
 
            return __markdown.markdown(source)
 
        except ImportError:
 
            log.warning('Install markdown to use this function')
 
            return cls.plain(source)
 

	
 

	
 
    @classmethod
 
    def rst(cls, source):
 
        source = safe_unicode(source)
 
        try:
 
            from docutils.core import publish_parts
 
            from docutils.parsers.rst import directives
 
            docutils_settings = dict([(alias, None) for alias in
 
                                cls.RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES])
 

	
 
            docutils_settings.update({'input_encoding': 'unicode',
 
                                      'report_level':4})
 

	
 
            for k, v in docutils_settings.iteritems():
 
                directives.register_directive(k, v)
 

	
 
            parts = publish_parts(source=source,
 
                                  writer_name="html4css1",
 
                                  settings_overrides=docutils_settings)
 

	
 
            return parts['html_title'] + parts["fragment"]
 
        except ImportError:
 
            log.warning('Install docutils to use this function')
 
            return cls.plain(source)
 

	
 
    @classmethod
 
    def rst_with_mentions(cls, source):
 
        mention_pat = re.compile(r'(?:^@|\s@)(\w+)')
 
        
 
        def wrapp(match_obj):
 
            uname = match_obj.groups()[0]
 
            return ' **@%(uname)s** ' % {'uname':uname}
 
        mention_hl = mention_pat.sub(wrapp, source).strip()
 
        return cls.rst(mention_hl)
 

	
rhodecode/model/notification.py
Show inline comments
 
@@ -11,193 +11,193 @@
 
    :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import os
 
import logging
 
import traceback
 
import datetime
 

	
 
from pylons.i18n.translation import _
 

	
 
import rhodecode
 
from rhodecode.lib import helpers as h
 
from rhodecode.model import BaseModel
 
from rhodecode.model.db import Notification, User, UserNotification
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class NotificationModel(BaseModel):
 

	
 
    def __get_user(self, user):
 
        if isinstance(user, basestring):
 
            return User.get_by_username(username=user)
 
        else:
 
            return self._get_instance(User, user)
 

	
 
    def __get_notification(self, notification):
 
        if isinstance(notification, Notification):
 
            return notification
 
        elif isinstance(notification, int):
 
            return Notification.get(notification)
 
        else:
 
            if notification:
 
                raise Exception('notification must be int or Instance'
 
                                ' of Notification got %s' % type(notification))
 

	
 
    def create(self, created_by, subject, body, recipients=None,
 
               type_=Notification.TYPE_MESSAGE, with_email=True,
 
               email_kwargs={}):
 
        """
 
        
 
        Creates notification of given type
 
        
 
        :param created_by: int, str or User instance. User who created this
 
            notification
 
        :param subject:
 
        :param body:
 
        :param recipients: list of int, str or User objects, when None 
 
            is given send to all admins
 
        :param type_: type of notification
 
        :param with_email: send email with this notification
 
        :param email_kwargs: additional dict to pass as args to email template
 
        """
 
        from rhodecode.lib.celerylib import tasks, run_task
 

	
 
        if recipients and not getattr(recipients, '__iter__', False):
 
            raise Exception('recipients must be a list of iterable')
 

	
 
        created_by_obj = self.__get_user(created_by)
 

	
 
        if recipients:
 
            recipients_objs = []
 
            for u in recipients:
 
                obj = self.__get_user(u)
 
                if obj:
 
                    recipients_objs.append(obj)
 
            recipients_objs = set(recipients_objs)
 
        else:
 
            # empty recipients means to all admins
 
            recipients_objs = User.query().filter(User.admin == True).all()
 

	
 
        notif = Notification.create(created_by=created_by_obj, subject=subject,
 
                                    body=body, recipients=recipients_objs,
 
                                    type_=type_)
 

	
 
        if with_email is False:
 
            return notif
 

	
 
        # send email with notification
 
        for rec in recipients_objs:
 
            email_subject = NotificationModel().make_description(notif, False)
 
            type_ = type_
 
            email_body = body
 
            kwargs = {'subject':subject, 'body':h.rst(body)}
 
            kwargs = {'subject':subject, 'body':h.rst_w_mentions(body)}
 
            kwargs.update(email_kwargs)
 
            email_body_html = EmailNotificationModel()\
 
                                .get_email_tmpl(type_, **kwargs)
 
            run_task(tasks.send_email, rec.email, email_subject, email_body,
 
                     email_body_html)
 

	
 
        return notif
 

	
 
    def delete(self, user, notification):
 
        # we don't want to remove actual notification just the assignment
 
        try:
 
            notification = self.__get_notification(notification)
 
            user = self.__get_user(user)
 
            if notification and user:
 
                obj = UserNotification.query()\
 
                        .filter(UserNotification.user == user)\
 
                        .filter(UserNotification.notification
 
                                == notification)\
 
                        .one()
 
                self.sa.delete(obj)
 
                return True
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def get_for_user(self, user):
 
        user = self.__get_user(user)
 
        return user.notifications
 

	
 
    def get_unread_cnt_for_user(self, user):
 
        user = self.__get_user(user)
 
        return UserNotification.query()\
 
                .filter(UserNotification.read == False)\
 
                .filter(UserNotification.user == user).count()
 

	
 
    def get_unread_for_user(self, user):
 
        user = self.__get_user(user)
 
        return [x.notification for x in UserNotification.query()\
 
                .filter(UserNotification.read == False)\
 
                .filter(UserNotification.user == user).all()]
 

	
 
    def get_user_notification(self, user, notification):
 
        user = self.__get_user(user)
 
        notification = self.__get_notification(notification)
 

	
 
        return UserNotification.query()\
 
            .filter(UserNotification.notification == notification)\
 
            .filter(UserNotification.user == user).scalar()
 

	
 
    def make_description(self, notification, show_age=True):
 
        """
 
        Creates a human readable description based on properties
 
        of notification object
 
        """
 

	
 
        _map = {notification.TYPE_CHANGESET_COMMENT:_('commented on commit'),
 
                notification.TYPE_MESSAGE:_('sent message'),
 
                notification.TYPE_MENTION:_('mentioned you'),
 
                notification.TYPE_REGISTRATION:_('registered in RhodeCode')}
 

	
 
        DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
 

	
 
        tmpl = "%(user)s %(action)s %(when)s"
 
        if show_age:
 
            when = h.age(notification.created_on)
 
        else:
 
            DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
 
            when = DTF(notification.created_on)
 
        data = dict(user=notification.created_by_user.username,
 
                    action=_map[notification.type_],
 
                    when=when)
 
        return tmpl % data
 

	
 

	
 
class EmailNotificationModel(BaseModel):
 

	
 
    TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
 
    TYPE_PASSWORD_RESET = 'passoword_link'
 
    TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
 
    TYPE_DEFAULT = 'default'
 

	
 
    def __init__(self):
 
        self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
 
        self._tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
 

	
 
        self.email_types = {
 
            self.TYPE_CHANGESET_COMMENT:'email_templates/changeset_comment.html',
 
            self.TYPE_PASSWORD_RESET:'email_templates/password_reset.html',
 
            self.TYPE_REGISTRATION:'email_templates/registration.html',
 
            self.TYPE_DEFAULT:'email_templates/default.html'
 
        }
 

	
 
    def get_email_tmpl(self, type_, **kwargs):
 
        """
 
        return generated template for email based on given type
 
        
rhodecode/templates/admin/notifications/show_notification.html
Show inline comments
 
## -*- coding: utf-8 -*-
 
<%inherit file="/base/base.html"/>
 

	
 
<%def name="title()">
 
    ${_('Show notification')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
 
</%def>
 

	
 
<%def name="breadcrumbs_links()">
 
    ${h.link_to(_('Notifications'),h.url('notifications'))}
 
    &raquo; 
 
    ${_('Show notification')}
 
</%def>
 

	
 
<%def name="page_nav()">
 
    ${self.menu('admin')}
 
</%def>
 

	
 
<%def name="main()">
 
<div class="box">
 
    <!-- box / title -->
 
    <div class="title">
 
        ${self.breadcrumbs()}       
 
        <ul class="links">
 
            <li>
 
              <span style="text-transform: uppercase;"><a href="#">${_('Compose message')}</a></span>
 
            </li>          
 
        </ul>            
 
    </div>
 
    <div class="table">
 
      <div id="notification_${c.notification.notification_id}">
 
        <div class="notification-header">
 
          <div class="gravatar">
 
              <img alt="gravatar" src="${h.gravatar_url(h.email(c.notification.created_by_user.email),24)}"/>
 
          </div>
 
          <div class="desc">
 
              ${c.notification.description}
 
          </div>
 
          <div class="delete-notifications">
 
            <span id="${c.notification.notification_id}" class="delete-notification delete_icon action"></span>
 
          </div>
 
        </div>
 
        <div>${h.rst(c.notification.body)}</div>
 
        <div>${h.rst_w_mentions(c.notification.body)}</div>
 
      </div>
 
    </div>
 
</div>
 
<script type="text/javascript">
 
var url = "${url('notification', notification_id='__NOTIFICATION_ID__')}";
 
var main = "${url('notifications')}";
 
   YUE.on(YUQ('.delete-notification'),'click',function(e){
 
       var notification_id = e.currentTarget.id;
 
       deleteNotification(url,notification_id,[function(){window.location=main}])
 
   })
 
</script>
 
</%def>  
rhodecode/templates/changeset/changeset_file_comment.html
Show inline comments
 
##usage:
 
## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
 
## ${comment.comment_block(co)}
 
##
 
<%def name="comment_block(co)">
 
  <div class="comment" id="comment-${co.comment_id}">
 
  	<div class="meta">
 
  		<span class="user">
 
  			<img src="${h.gravatar_url(co.author.email, 20)}" />
 
  			${co.author.username}
 
  		</span>
 
  		<a href="${h.url.current(anchor='comment-%s' % co.comment_id)}"> ${_('commented on')} </a>
 
  		${h.short_id(co.revision)}
 
  		%if co.f_path:
 
  			${_(' in file ')}
 
  			${co.f_path}:L ${co.line_no}
 
  		%endif
 
  		<span class="date">
 
  			${h.age(co.modified_at)}
 
  		</span>
 
  	</div>
 
  	<div class="text">
 
  		%if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id:
 
  			<div class="buttons">
 
  				<span onClick="deleteComment(${co.comment_id})" class="delete-comment ui-btn">${_('Delete')}</span>
 
  			</div>
 
  		%endif
 
  		${h.rst(co.text)|n}
 
  		${h.rst_w_mentions(co.text)|n}
 
  	</div>
 
  </div>
 
</%def>
 
 
 
 
<%def name="comment_inline_form()">
 
<div id='comment-inline-form-template' style="display:none">
 
  <div class="comment-inline-form">
 
  %if c.rhodecode_user.username != 'default':
 
      ${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=c.changeset.raw_id))}
 
      <div class="clearfix">
 
          <div class="comment-help">${_('Commenting on line')} {1} ${_('comments parsed using')} 
 
          <a href="${h.url('rst_help')}">RST</a> ${_('syntax')}</div>
 
              <textarea id="text_{1}" name="text"></textarea>
 
      </div>
 
      <div class="comment-button">
 
      <input type="hidden" name="f_path" value="{0}">
 
      <input type="hidden" name="line" value="{1}">            
 
      ${h.submit('save', _('Comment'), class_='ui-btn')}
 
      ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
 
      </div>
 
      ${h.end_form()}
 
  %else:
 
      ${h.form('')}
 
      <div class="clearfix">
 
          <div class="comment-help">
 
            ${'You need to be logged in to comment.'} <a href="${h.url('login_home',came_from=h.url.current())}">${_('Login now')}</a>
 
          </div>
 
      </div>
 
      <div class="comment-button">
 
      ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
 
      </div>
 
      ${h.end_form()}  
 
  %endif      
 
  </div>
 
</div>  
 
</%def>
 
\ No newline at end of file
0 comments (0 inline, 0 general)