Browse Source

- User profiles display more information
- News feed works
- Little fixes in UserCP

Ralfp 12 years ago
parent
commit
1267f5aeb3

+ 1 - 1
misago/alerts/views.py

@@ -48,4 +48,4 @@ def show_alerts(request):
                                              'new_alerts': new_alerts,
                                              'alerts': alerts,
                                              },
-                                            context_instance=RequestContext(request));
+                                            context_instance=RequestContext(request))

+ 30 - 0
misago/markdown/factory.py

@@ -1,10 +1,40 @@
 import re
 import markdown
+from HTMLParser import HTMLParser
 from django.conf import settings
 from django.utils.importlib import import_module
 from django.utils.translation import ugettext_lazy as _
 from misago.utils import get_random_string
 
+class ClearHTMLParser(HTMLParser):
+    def __init__(self):
+        HTMLParser.__init__(self)
+        self.clean_text = ''
+        
+    def handle_starttag(self, tag, attrs):
+        try:
+            if tag == 'img':
+                for attr in attrs:
+                    if attr[0] == 'src':
+                        self.clean_text += attr[1]
+            if tag == 'a':
+                for attr in attrs:
+                    if attr[0] == 'href':
+                        self.clean_text += attr[1]
+        except IndexError, KeyError:
+            pass
+        print "Encountered a start tag %s with attrs %s" % (tag, attrs)
+        
+    def handle_data(self, data):
+        self.clean_text += data
+
+
+def clear_markdown(text):
+    parser = ClearHTMLParser()
+    parser.feed(text)
+    return parser.clean_text
+
+
 def remove_unsupported(md):
     # References are evil, we dont support them
     del md.preprocessors['reference']

+ 22 - 1
misago/newsfeed/views.py

@@ -1,2 +1,23 @@
+from django.template import RequestContext
+from misago.authn.decorators import block_guest
+from misago.threads.models import Post
+from misago.views import error404
+
 def newsfeed(request):
-    pass
+    follows = []
+    for user in request.user.follows.iterator():
+        follows.append(user.pk)
+    print follows
+    queryset = []
+    if follows:
+        queryset = Post.objects.filter(forum_id__in=request.acl.threads.get_readable_forums(request.acl))
+        queryset = queryset.filter(deleted=False).filter(moderated=False)
+        queryset = queryset.filter(user_id__in=follows)
+        queryset = queryset.prefetch_related('thread', 'forum', 'user').order_by('-id')
+        queryset = queryset[:18]
+    return request.theme.render_to_response('newsfeed.html',
+                                            {
+                                             'follows': follows,
+                                             'posts': queryset,
+                                             },
+                                            context_instance=RequestContext(request))

+ 14 - 1
misago/profiles/decorators.py

@@ -19,4 +19,17 @@ def profile_view(fallback='user'):
                 return error404(request)
     
         return wraps(f)(inner_decorator)
-    return outer_decorator
+    return outer_decorator
+
+
+def user_view(f):
+    def inner_decorator(request, user, *args, **kwargs):
+        request = request
+        user_pk = int(user)
+        try:
+            user = User.objects.get(pk=user_pk)
+            return f(request, user, *args, **kwargs)
+        except User.DoesNotExist:
+            return error404(request)
+
+    return wraps(f)(inner_decorator)

+ 2 - 0
misago/profiles/followers/urls.py

@@ -6,9 +6,11 @@ def register_profile_urls(first=False):
         urlpatterns += patterns('misago.profiles.followers.views',
             url(r'^$', 'followers', name="user"),
             url(r'^$', 'followers', name="user_followers"),
+            url(r'^(?P<page>\d+)/$', 'followers', name="user_followers"),
         )
     else:
         urlpatterns += patterns('misago.profiles.followers.views',
             url(r'^followers/$', 'followers', name="user_followers"),
+            url(r'^followers/(?P<page>\d+)/$', 'followers', name="user_followers"),
         )
     return urlpatterns

+ 9 - 2
misago/profiles/followers/views.py

@@ -1,10 +1,17 @@
 from misago.profiles.decorators import profile_view
 from misago.profiles.template import RequestContext
+from misago.utils import make_pagination
 
 @profile_view('user_followers')
-def followers(request, user):
-    return request.theme.render_to_response('profiles/profile.html',
+def followers(request, user, page=0):
+    queryset = user.follows_set.order_by('username_slug')
+    count = queryset.count()
+    pagination = make_pagination(page, count, 40)
+    return request.theme.render_to_response('profiles/followers.html',
                                             context_instance=RequestContext(request, {
                                              'profile': user,
                                              'tab': 'followers',
+                                             'items_total': count,
+                                             'items': queryset[pagination['start']:pagination['stop']],
+                                             'pagination': pagination,
                                              }));

+ 2 - 0
misago/profiles/follows/urls.py

@@ -6,9 +6,11 @@ def register_profile_urls(first=False):
         urlpatterns += patterns('misago.profiles.follows.views',
             url(r'^$', 'follows', name="user"),
             url(r'^$', 'follows', name="user_follows"),
+            url(r'^(?P<page>\d+)/$', 'follows', name="user_follows"),
         )
     else:
         urlpatterns += patterns('misago.profiles.follows.views',
             url(r'^follows/$', 'follows', name="user_follows"),
+            url(r'^follows/(?P<page>\d+)/$', 'follows', name="user_follows"),
         )
     return urlpatterns

+ 9 - 2
misago/profiles/follows/views.py

@@ -1,10 +1,17 @@
 from misago.profiles.decorators import profile_view
 from misago.profiles.template import RequestContext
+from misago.utils import make_pagination
 
 @profile_view('user_follows')
-def follows(request, user):
-    return request.theme.render_to_response('profiles/profile.html',
+def follows(request, user, page=0):
+    queryset = user.follows.order_by('username_slug')
+    count = queryset.count()
+    pagination = make_pagination(page, count, 40)
+    return request.theme.render_to_response('profiles/follows.html',
                                             context_instance=RequestContext(request, {
                                              'profile': user,
                                              'tab': 'follows',
+                                             'items_total': count,
+                                             'items': queryset[pagination['start']:pagination['stop']],
+                                             'pagination': pagination,
                                              }));

+ 10 - 0
misago/profiles/template.py

@@ -1,10 +1,20 @@
 from django.conf import settings
 from django.template import RequestContext as DjangoRequestContext
 from django.utils.importlib import import_module
+from misago.users.models import User
 
 def RequestContext(request, context=None):
     if not context:
         context = {}
+    context['fallback'] = request.path
+        
+    # Find out if we ignore or follow this user
+    context['follows'] = False
+    context['ignores'] = False
+    if request.user.is_authenticated() and request.user.pk != context['profile'].pk:
+        context['follows'] = request.user.is_following(context['profile'])
+        context['ignores'] = request.user.is_ignoring(context['profile'])
+
     context['tabs'] = []
     for extension in settings.PROFILE_EXTENSIONS:
         profile_module = import_module(extension + '.profile')

+ 1 - 1
misago/profiles/views.py

@@ -72,4 +72,4 @@ def list(request, rank_slug=None):
                                          'ranks': ranks,
                                          'users': users,
                                         },
-                                        context_instance=RequestContext(request));
+                                        context_instance=RequestContext(request));

+ 2 - 0
misago/template/templatetags/django2jinja.py

@@ -44,6 +44,8 @@ def parse_markdown(value, format=None):
 
 @register.filter(name='markdown_short')
 def short_markdown(value, length=300):
+    from misago.markdown.factory import clear_markdown
+    value = clear_markdown(value)
     if len(value) <= length:
         return ' '.join(value.splitlines())
     value = ' '.join(value.splitlines())

+ 0 - 1
misago/usercp/template.py

@@ -6,7 +6,6 @@ def RequestContext(request, context=None):
     if not context:
         context = {}
     context['tabs'] = []
-
     for extension in settings.USERCP_EXTENSIONS:
         usercp_module = import_module(extension + '.usercp')
         try:

+ 7 - 0
misago/usercp/urls.py

@@ -13,3 +13,10 @@ for extension in settings.USERCP_EXTENSIONS:
         )
     except AttributeError:
         pass
+
+urlpatterns += patterns('misago.usercp.views',
+    url(r'^follow/(?P<user>\d+)/$', 'follow', name="follow_user"),
+    url(r'^unfollow/(?P<user>\d+)/$', 'unfollow', name="unfollow_user"),
+    url(r'^ignore/(?P<user>\d+)/$', 'ignore', name="ignore_user"),
+    url(r'^unignore/(?P<user>\d+)/$', 'unignore', name="unignore_user"),
+)

+ 75 - 0
misago/usercp/views.py

@@ -0,0 +1,75 @@
+from django.core.urlresolvers import NoReverseMatch
+from django.shortcuts import redirect
+from django.utils.translation import ugettext as _
+from misago.authn.decorators import block_guest
+from misago.csrf.decorators import check_csrf
+from misago.messages import Message
+from misago.users.models import User
+from misago.profiles.decorators import user_view
+from misago.views import error404
+from misago.utils import ugettext_lazy
+
+def fallback(request):
+    try:
+        return redirect(request.POST.get('fallback', '/'))
+    except NoReverseMatch:
+        return redirect('index')
+
+@block_guest
+@check_csrf
+@user_view
+def follow(request, user):
+    if request.user.pk == user.pk:
+        return error404(request)
+    if not request.user.is_following(user):
+        request.messages.set_flash(Message(_("You are now following %(username)s") % {'username': user.username}), 'success')
+        request.user.follows.add(user)
+        request.user.following += 1
+        request.user.save(force_update=True)
+        user.followers += 1
+        if not user.is_ignoring(request.user):
+            alert = user.alert(ugettext_lazy("%(username)s is now following you").message)
+            alert.profile('username', request.user)
+            alert.save_all()
+        else:
+            user.save(force_update=True)
+    return fallback(request)
+
+
+@block_guest
+@check_csrf
+@user_view
+def unfollow(request, user):
+    if request.user.pk == user.pk:
+        return error404(request)
+    if request.user.is_following(user):
+        request.messages.set_flash(Message(_("You have stopped following %(username)s") % {'username': user.username}))
+        request.user.follows.remove(user)
+        request.user.following -= 1
+        request.user.save(force_update=True)
+        user.followers -= 1
+        user.save(force_update=True)
+    return fallback(request)
+
+
+@block_guest
+@check_csrf
+@user_view
+def ignore(request, user):
+    if request.user.pk == user.pk:
+        return error404(request)
+    if not request.user.is_ignoring(user):
+        request.messages.set_flash(Message(_("You are now ignoring %(username)s") % {'username': user.username}), 'success')
+        request.user.ignores.add(user)
+    return fallback(request)
+
+
+@block_guest
+@check_csrf
+def unignore(request, user):
+    if request.user.pk == user.pk:
+        return error404(request)
+    if request.user.is_ignoring(user):
+        request.messages.set_flash(Message(_("You have stopped ignoring %(username)s") % {'username': user.username}))
+        request.user.ignores.remove(user)
+    return fallback(request)

+ 12 - 0
misago/users/models.py

@@ -386,6 +386,18 @@ class User(models.Model):
             raw_password = password_reversed
         return check_password(raw_password, self.password, setter)
 
+    def is_following(self, user):
+        try:
+            return self.follows.filter(id=user.pk).count() > 0
+        except AttributeError:
+            return self.follows.filter(id=user).count() > 0
+
+    def is_ignoring(self, user):
+        try:
+            return self.ignores.filter(id=user.pk).count() > 0
+        except AttributeError:
+            return self.ignores.filter(id=user).count() > 0
+
     def get_roles(self):
         return self.roles.all()
 

+ 14 - 8
static/sora/css/sora.css

@@ -901,6 +901,7 @@ td.lead-cell{color:#555555;font-weight:bold;}
 .header-tabbed{border-bottom:none;padding-bottom:0px;margin-bottom:0px;}.header-tabbed .nav-tabs li a:link,.header-tabbed .nav-tabs li a:active,.header-tabbed .nav-tabs li a:visited{font-weight:bold;}
 .header-tabbed .nav-tabs li.active a:link,.header-tabbed .nav-tabs li.active a:active,.header-tabbed .nav-tabs li.active a:visited,.header-tabbed .nav-tabs li.active a:hover{background-color:#fcfcfc;border-bottom:4px solid #0088cc;border-width:0px 0px 4px 0px;padding-top:9px;padding-bottom:5px;}
 .header-tabbed .nav-tabs li.fallback{float:right;}.header-tabbed .nav-tabs li.fallback a:link,.header-tabbed .nav-tabs li.fallback a:active,.header-tabbed .nav-tabs li.fallback a:visited,.header-tabbed .nav-tabs li.fallback a:hover{border-bottom:none;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;margin-top:4px;padding:4px 12px;}
+.tabs-extra{float:right;margin:0px;}
 .tabs-left{background-color:#f7f7f7;-webkit-border-radius:3px 0px 0px 3px;-moz-border-radius:3px 0px 0px 3px;border-radius:3px 0px 0px 3px;padding-left:8px;}.tabs-left ul{margin-bottom:0px;width:100%;}.tabs-left ul li.nav-header{font-size:100%;}
 .tabs-left ul li a,.tabs-left ul li a:link,.tabs-left ul li a:active,.tabs-left ul li a:visited{opacity:0.8;filter:alpha(opacity=80);font-weight:bold;}
 .tabs-left ul li a:hover{opacity:0.9;filter:alpha(opacity=90);}
@@ -909,14 +910,18 @@ td.lead-cell{color:#555555;font-weight:bold;}
 .sidetabs-content{padding-top:0px;margin-top:-12px;}
 .list-nav{overflow:auto;margin-bottom:4px;}.list-nav .nav-pills{margin:0px;}
 .list-nav.last{margin-top:-8px;}
-.nav-pills li{margin-left:8px;}.nav-pills li.info a:link,.nav-pills li.danger a:link,.nav-pills li.primary a:link,.nav-pills li.info a:visited,.nav-pills li.danger a:visited,.nav-pills li.primary a:visited{font-weight:bold;}
-.nav-pills li.info a:link,.nav-pills li.info a:visited{background-color:#eeeeee;color:#555555;}.nav-pills li.info a:link i,.nav-pills li.info a:visited i{opacity:0.6;filter:alpha(opacity=60);}
-.nav-pills li.info a:active,.nav-pills li.info a:hover{background-color:#555555 !important;color:#ffffff;}.nav-pills li.info a:active i,.nav-pills li.info a:hover i{background-image:url("../img/glyphicons-halflings-white.png");opacity:1;filter:alpha(opacity=100);}
-.nav-pills li.danger a:link,.nav-pills li.primary a:link,.nav-pills li.danger a:visited,.nav-pills li.primary a:visited{color:#ffffff;}.nav-pills li.danger a:link i,.nav-pills li.primary a:link i,.nav-pills li.danger a:visited i,.nav-pills li.primary a:visited i{background-image:url("../img/glyphicons-halflings-white.png");}
-.nav-pills li.danger a:link,.nav-pills li.danger a:visited{background-color:#dc4e44;}
-.nav-pills li.danger a:active,.nav-pills li.danger a:hover{background-color:#9d261d !important;}
-.nav-pills li.primary a:link,.nav-pills li.primary a:visited{background-color:#0da6f2;}
-.nav-pills li.primary a:active,.nav-pills li.primary a:hover{background-color:#0088cc !important;}
+.nav-pills li{margin-left:8px;}.nav-pills li form{margin:0px;padding:0px;}
+.nav-pills li button,.nav-pills li button:hover,.nav-pills li button:active{border:none;}
+.nav-pills li.info a:link,.nav-pills li.danger a:link,.nav-pills li.primary a:link,.nav-pills li.success a:link,.nav-pills li.info a:visited,.nav-pills li.danger a:visited,.nav-pills li.primary a:visited,.nav-pills li.success a:visited{font-weight:bold;}
+.nav-pills li.info a:link,.nav-pills li.info a:visited,.nav-pills li.info button{background-color:#eeeeee;color:#555555;}.nav-pills li.info a:link i,.nav-pills li.info a:visited i,.nav-pills li.info button i{opacity:0.6;filter:alpha(opacity=60);}
+.nav-pills li.info a:active,.nav-pills li.info a:hover,.nav-pills li.info button:active,.nav-pills li.info button:hover{background-color:#555555 !important;color:#ffffff;}.nav-pills li.info a:active i,.nav-pills li.info a:hover i,.nav-pills li.info button:active i,.nav-pills li.info button:hover i{background-image:url("../img/glyphicons-halflings-white.png");opacity:1;filter:alpha(opacity=100);}
+.nav-pills li.danger a:link,.nav-pills li.primary a:link,.nav-pills li.success a:link,.nav-pills li.danger a:visited,.nav-pills li.primary a:visited,.nav-pills li.success a:visited,.nav-pills li.danger button,.nav-pills li.primary button,.nav-pills li.success button{color:#ffffff;}.nav-pills li.danger a:link i,.nav-pills li.primary a:link i,.nav-pills li.success a:link i,.nav-pills li.danger a:visited i,.nav-pills li.primary a:visited i,.nav-pills li.success a:visited i,.nav-pills li.danger button i,.nav-pills li.primary button i,.nav-pills li.success button i{background-image:url("../img/glyphicons-halflings-white.png");}
+.nav-pills li.danger a:link,.nav-pills li.danger a:visited,.nav-pills li.danger button{background-color:#dc4e44 !important;}
+.nav-pills li.danger a:active,.nav-pills li.danger a:hover,.nav-pills li.danger button:active,.nav-pills li.danger button:hover{background-color:#9d261d !important;}
+.nav-pills li.primary a:link,.nav-pills li.primary a:visited,.nav-pills li.primary button{background-color:#0da6f2 !important;}
+.nav-pills li.primary a:active,.nav-pills li.primary a:hover,.nav-pills li.primary button:active,.nav-pills li.primary button:hover{background-color:#0088cc !important;}
+.nav-pills li.success a:link,.nav-pills li.success a:visited,.nav-pills li.success button{background-color:#86cb86 !important;}
+.nav-pills li.success a:active,.nav-pills li.success a:hover,.nav-pills li.success button:active,.nav-pills li.success button:hover{background-color:#46a546 !important;}
 .pager{margin:0px 0px;margin-top:6px;padding:0px;margin-right:6px;}.pager>li{margin-right:6px;}.pager>li>a:link,.pager>li>a:active,.pager>li>a:visited{background:#e8e8e8;border:none;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;padding:2px 5px;color:#333333;font-weight:bold;}
 .pager>li>a:hover{background-color:#0088cc;color:#ffffff;}.pager>li>a:hover i{background-image:url("../img/glyphicons-halflings-white.png");}
 .pager>li.unread>a:link,.pager>li.unread>a:active,.pager>li.unread>a:visited{background:#c67605;color:#ffffff;}.pager>li.unread>a:link i,.pager>li.unread>a:active i,.pager>li.unread>a:visited i{background-image:url("../img/glyphicons-halflings-white.png");}
@@ -1036,6 +1041,7 @@ td.lead-cell{color:#555555;font-weight:bold;}
 .profile-header{overflow:auto;border-bottom:none;margin-bottom:30px;}.profile-header .avatar-height{overflow:auto;}.profile-header .avatar-height .avatar{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;margin-right:24px;width:125px;height:125px;}
 .profile-header .avatar-height h1{font-size:300%;}
 .profile-header .avatar-height .lead{color:#555555;}.profile-header .avatar-height .lead .muted{color:#959595;}
+.profile-header .tabs-extra{margin-top:-33px;}
 .profile-header .nav-tabs{margin-top:-22px;margin-bottom:0px;padding-left:142px;}
 .avatar-menu h3{margin-top:0px;}
 .alerts-list a{font-weight:bold;}

+ 36 - 11
static/sora/css/sora/navs.less

@@ -33,6 +33,12 @@
   }
 }
 
+// Tabs extra
+.tabs-extra {
+  float: right;
+  margin: 0px;
+}
+
 // Tabs on left
 .tabs-left {
   background-color: darken(@bodyBackground, 2%);
@@ -103,14 +109,23 @@
   li {
     margin-left: 8px;
     
-    &.info, &.danger, &.primary {
+    form {
+      margin: 0px;
+      padding: 0px;
+    }
+    
+    button, button:hover, button:active {
+      border: none;
+    }
+      
+    &.info, &.danger, &.primary, &.success {
       a:link, a:visited {
         font-weight: bold;
       }
     }
     
     &.info {
-      a:link, a:visited {
+      a:link, a:visited, button {
         background-color: @grayLighter;
         
         color: @gray;
@@ -120,7 +135,7 @@
         }
       }
       
-      a:active, a:hover {
+      a:active, a:hover, button:active, button:hover {
         background-color: @gray !important;
         color: @white;
             
@@ -131,8 +146,8 @@
       }
     }
     
-    &.danger, &.primary {
-      a:link, a:visited {
+    &.danger, &.primary, &.success {
+      a:link, a:visited, button {
         color: @white;
               
         i {
@@ -142,24 +157,34 @@
     }
     
     &.danger {
-      a:link, a:visited {
-        background-color: lighten(@red, 20%);
+      a:link, a:visited, button {
+        background-color: lighten(@red, 20%) !important;
       }
       
-      a:active, a:hover {
+      a:active, a:hover, button:active, button:hover {
         background-color: @red !important;
       }
     }
     
     &.primary {
-      a:link, a:visited {
-        background-color: desaturate(lighten(@linkColor, 10%), 10%);
+      a:link, a:visited, button {
+        background-color: desaturate(lighten(@linkColor, 10%), 10%) !important;
       }
       
-      a:active, a:hover {
+      a:active, a:hover, button:active, button:hover {
         background-color: @linkColor !important;
       }
     }
+    
+    &.success {
+      a:link, a:visited, button {
+        background-color: lighten(@green, 20%) !important;
+      }
+      
+      a:active, a:hover, button:active, button:hover {
+        background-color: @green !important;
+      }
+    }
   }
 }
 

+ 4 - 0
static/sora/css/sora/utilities.less

@@ -32,6 +32,10 @@
     }
   }
   
+  .tabs-extra {
+    margin-top: -33px;
+  }
+  
   .nav-tabs {
     margin-top: -22px;
     margin-bottom: 0px;

+ 52 - 0
templates/sora/newsfeed.html

@@ -0,0 +1,52 @@
+{% extends "sora/profiles/profile.html" %}
+{% load i18n %}
+{% load humanize %}
+{% load url from future %}
+{% import "sora/macros.html" as macros with context %}
+
+{% block title %}{{ macros.page_title(_('Your News Feed')) }}{% endblock %}
+
+{% block content %}
+<div class="page-header">
+  <h1>{% trans %}Your News Feed{% endtrans %}</h1>
+</div>
+
+{% if follows %}
+<p class="lead">{% trans count=follows|length -%}
+    You are following one member, see what he posted recently:
+    {%- pluralize -%}
+    You are following {{ count }} members, see what they posted recently:
+    {%- endtrans %}</p>
+{% if posts %}
+<ul class="unstyled shorts-list">
+  {% for item in posts %}
+  <li>
+    <img src="{{ item.user.get_avatar(36) }}" class="avatar">
+    <p class="message">{{ item.post_preparsed|markdown_short(300) }}</p>
+    <p class="location">{% if item.thread.start_post_id == item.pk -%}
+        {% trans thread=thread(item), forum=forum(item.forum), user=username(item.user), date=item.date|reldate|low %}Thread {{ thread }} posted in {{ forum }} by {{ user }} {{ date }}{% endtrans %}
+        {%- else -%}
+        {% trans thread=thread(item), forum=forum(item.forum), user=username(item.user), date=item.date|reldate|low %}Reply to {{ thread }} posted in {{ forum }} by {{ user }} {{ date }}{% endtrans %}
+        {%- endif %}</p>
+  </li>
+  {% endfor %}
+</ul>
+{% else %}
+<p class="lead">{% trans username=user.username %}{{ username }}, there is nothing to display in your news feed... yet!{% endtrans %}</p>
+{% endif %}
+{% else %}
+<p class="lead">{% trans username=user.username %}{{ username }}, you have to follow other users in order to fill your news feed.{% endtrans %}</p>
+{% endif %}
+{% endblock %}
+
+{% macro thread(item) -%}
+<a href="{% url 'thread_find' thread=item.thread.pk, slug=item.thread.slug, post=item.pk %}">{{ item.thread.name }}</a>
+{%- endmacro %}
+
+{% macro forum(forum) -%}
+<a href="{% url 'forum' forum=forum.pk, slug=forum.slug %}">{{ forum.name }}</a>
+{%- endmacro %}
+
+{% macro username(user) -%}
+<a href="{% url 'user' user=user.pk, username=user.username_slug %}">{{ user.username }}</a>
+{%- endmacro %}

+ 1 - 1
templates/sora/profiles/content_posts.html

@@ -30,7 +30,7 @@
   {% for item in items %}
   <li>
     <img src="{{ profile.get_avatar(36) }}" class="avatar">
-    <p class="message">{{ item.post|markdown_short(300) }}</p>
+    <p class="message">{{ item.post_preparsed|markdown_short(300) }}</p>
     <p class="location">{% if item.thread.start_post_id == item.pk -%}
         {% trans thread=thread(item), forum=forum(item.forum), user=username(profile), date=item.date|reldate|low %}Thread {{ thread }} posted in {{ forum }} by {{ user }} {{ date }}{% endtrans %}
         {%- else -%}

+ 1 - 1
templates/sora/profiles/content_threads.html

@@ -30,7 +30,7 @@
   {% for item in items %}
   <li>
     <img src="{{ profile.get_avatar(36) }}" class="avatar">
-    <p class="message">{{ item.start_post.post|markdown_short(300) }}</p>
+    <p class="message">{{ item.start_post.post_preparsed|markdown_short(300) }}</p>
     <p class="location">{% trans thread=thread(item), forum=forum(item.forum), user=username(profile), date=item.start|reldate|low %}Thread {{ thread }} posted in {{ forum }} by {{ user }} {{ date }}{% endtrans %}</p>
   </li>
   {% endfor %}

+ 69 - 0
templates/sora/profiles/followers.html

@@ -0,0 +1,69 @@
+{% extends "sora/profiles/profile.html" %}
+{% load i18n %}
+{% load humanize %}
+{% load url from future %}
+{% import "_forms.html" as form_theme with context %}
+{% import "sora/macros.html" as macros with context %}
+
+{% block title %}{{ macros.page_title(_('Followers'), profile.username) }}{% endblock %}
+
+{% block tab %}
+<h2>{% trans username=profile.username %}{{ username }}'s Followers{% endtrans %} <small>{% if items_total -%}
+    {% trans count=items_total, total=items_total|intcomma, username=profile.username -%}
+    {{ username }} has one follower
+    {%- pluralize -%}
+    {{ username }} has {{ total }} followers
+    {%- endtrans %}
+    {%- else -%}
+    {% trans username=profile.username %}{{ username }} has no followers{% endtrans %}
+    {%- endif %}</small></h2>
+
+{% if items_total %}
+<div class="list-nav">
+  {{ pager() }}
+</div>
+<table class="table table-striped table-users">
+  <thead>
+    <tr>
+      <th colspan="4">{% trans count=items_total, total=items_total|intcomma, username=profile.username -%}
+    {{ username }} has one follower
+    {%- pluralize -%}
+    {{ username }} has {{ total }} followers
+    {%- endtrans %}</th>
+    </tr>
+  </thead>
+  <tbody>
+    {% for row in items|batch(4, '') %}
+    <tr>
+      {% for user in row %}
+      <td class="span3">
+        {% if user %}
+        <a href="{% url 'user' username=user.username_slug, user=user.pk %}"><img src="{{ user.get_avatar(42) }}" class="avatar" alt=""> <strong>{{ user.username }}</strong>{% if user.title or (in_search and user.get_title()) %} <span class="muted">{% if in_search%}{{ _(user.get_title()) }}{% else %}{{ _(user.title) }}{% endif %}</span>{% endif %}</a>
+        {% else %}
+        &nbsp;
+        {% endif %}
+      </td>
+      {% endfor %}
+    </tr>
+    {% endfor %}
+  </tbody>
+</table>
+<div class="list-nav">
+  {{ pager() }}
+</div>
+{% else %}
+<p class="lead">{% trans username=profile.username %}{{ username }} has no followers{% endtrans %}</p>
+{% endif %}
+{% endblock %}
+
+{% macro pager() -%}
+  <ul class="pager pull-left">
+    {%- if pagination['prev'] > 0 %}<li><a href="{%- if pagination['prev'] > 1 %}{% url 'user_followers' user=profile.id, username=profile.username_slug, page=pagination['prev'] %}{% else %}{% url 'user_followers' user=profile.id, username=profile.username_slug %}{% endif %}" class="tooltip-top" title="{% trans %}Previous Page{% endtrans %}"><i class="icon-chevron-left"></i></a></li>{% endif -%}
+    {%- if pagination['next'] > 0 %}<li><a href="{% url 'user_followers' user=profile.id, username=profile.username_slug, page=pagination['next'] %}" class="tooltip-top" title="{% trans %}Next Page{% endtrans %}"><i class="icon-chevron-right"></i></a></li>{% endif -%}
+    <li class="count">
+    {%- trans current_page=pagination['page'], pages=pagination['total'] -%}
+    Page {{ current_page }} of {{ pages }}
+    {%- endtrans -%}
+    </li>
+  </ul>
+{%- endmacro %}

+ 69 - 0
templates/sora/profiles/follows.html

@@ -0,0 +1,69 @@
+{% extends "sora/profiles/profile.html" %}
+{% load i18n %}
+{% load humanize %}
+{% load url from future %}
+{% import "_forms.html" as form_theme with context %}
+{% import "sora/macros.html" as macros with context %}
+
+{% block title %}{{ macros.page_title(_('Follows'), profile.username) }}{% endblock %}
+
+{% block tab %}
+<h2>{% trans username=profile.username %}Who {{ username }} Follows{% endtrans %} <small>{% if items_total -%}
+    {% trans count=items_total, total=items_total|intcomma, username=profile.username -%}
+    {{ username }} follows one member
+    {%- pluralize -%}
+    {{ username }} follows {{ total }} members
+    {%- endtrans %}
+    {%- else -%}
+    {% trans username=profile.username %}{{ username }} follows nobody{% endtrans %}
+    {%- endif %}</small></h2>
+
+{% if items_total %}
+<div class="list-nav">
+  {{ pager() }}
+</div>
+<table class="table table-striped table-users">
+  <thead>
+    <tr>
+      <th colspan="4">{% trans count=items_total, total=items_total|intcomma, username=profile.username -%}
+    {{ username }} follows one member
+    {%- pluralize -%}
+    {{ username }} follows {{ total }} members
+    {%- endtrans %}</th>
+    </tr>
+  </thead>
+  <tbody>
+    {% for row in items|batch(4, '') %}
+    <tr>
+      {% for user in row %}
+      <td class="span3">
+        {% if user %}
+        <a href="{% url 'user' username=user.username_slug, user=user.pk %}"><img src="{{ user.get_avatar(42) }}" class="avatar" alt=""> <strong>{{ user.username }}</strong>{% if user.title or (in_search and user.get_title()) %} <span class="muted">{% if in_search%}{{ _(user.get_title()) }}{% else %}{{ _(user.title) }}{% endif %}</span>{% endif %}</a>
+        {% else %}
+        &nbsp;
+        {% endif %}
+      </td>
+      {% endfor %}
+    </tr>
+    {% endfor %}
+  </tbody>
+</table>
+<div class="list-nav">
+  {{ pager() }}
+</div>
+{% else %}
+<p class="lead">{% trans username=profile.username %}{{ username }} follows nobody{% endtrans %}</p>
+{% endif %}
+{% endblock %}
+
+{% macro pager() -%}
+  <ul class="pager pull-left">
+    {%- if pagination['prev'] > 0 %}<li><a href="{%- if pagination['prev'] > 1 %}{% url 'user_follows' user=profile.id, username=profile.username_slug, page=pagination['prev'] %}{% else %}{% url 'user_follows' user=profile.id, username=profile.username_slug %}{% endif %}" class="tooltip-top" title="{% trans %}Previous Page{% endtrans %}"><i class="icon-chevron-left"></i></a></li>{% endif -%}
+    {%- if pagination['next'] > 0 %}<li><a href="{% url 'user_follows' user=profile.id, username=profile.username_slug, page=pagination['next'] %}" class="tooltip-top" title="{% trans %}Next Page{% endtrans %}"><i class="icon-chevron-right"></i></a></li>{% endif -%}
+    <li class="count">
+    {%- trans current_page=pagination['page'], pages=pagination['total'] -%}
+    Page {{ current_page }} of {{ pages }}
+    {%- endtrans -%}
+    </li>
+  </ul>
+{%- endmacro %}

+ 29 - 4
templates/sora/profiles/profile.html

@@ -14,17 +14,42 @@
       <img src="{{ profile.get_avatar() }}" class="avatar pull-left" alt="">
       <div class="pull-left">
         <h1>{{ profile.username }}</h1>
-        <p class="lead">{% if profile.title or profile.rank.title %}{% if profile.title %}{{ _(profile.title) }}{% elif profile.rank.title %}{{ _(profile.rank.title) }}{% endif %}; {% endif %}<span class="muted">{% trans last_visit=profile.last_date|reltimesince|low %}Last seen {{ last_visit }}{% endtrans %}</span></p>
+        <p class="lead">
+          {% if profile.title or profile.rank.title %}{% if profile.title %}{{ _(profile.title) }}{% elif profile.rank.title %}{{ _(profile.rank.title) }}{% endif %}; {% endif %}<span class="muted">{% trans last_visit=profile.last_date|reltimesince|low %}Last seen {{ last_visit }}{% endtrans %}</span>
+        </p>
       </div>
-    </div>	
+    </div>
     <ul class="nav nav-tabs">
       {% for link in tabs %}
-      <li{% if link.active %} class="active"{% endif %}><a href="{{ link.route|url(user=profile.pk, username=profile.username_slug) }}">{{ link.name }}</a></li>
+      <li{% if link.active %} class="active"{% endif %}>
+        <a href="{{ link.route|url(user=profile.pk, username=profile.username_slug) }}">{{ link.name }}</a></li>
       {% endfor %}
     </ul>
+    {% if user.is_authenticated() and user.pk != profile.pk %}
+    <ul class="nav nav-pills tabs-extra">
+      <li class="{% if ignores %}danger{% else %}info{% endif %}">
+        <form action="{% if ignores %}{% url 'unignore_user' user=profile.id %}{% else %}{% url 'ignore_user' user=profile.id %}{% endif %}" method="post">
+          <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+          <input type="hidden" name="fallback" value="{{ fallback }}">
+          <button type="submit" class="btn">
+            <i class="icon-ban-circle"></i> {% if ignores %}{% trans %}Don't Ignore{% endtrans %}{% else %}{% trans %}Ignore{% endtrans %}{% endif %}
+          </button>
+        </form>
+      </li>
+      <li class="{% if follows %}success{% else %}info{% endif %}">
+        <form action="{% if follows %}{% url 'unfollow_user' user=profile.id %}{% else %}{% url 'follow_user' user=profile.id %}{% endif %}" method="post">
+          <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+          <input type="hidden" name="fallback" value="{{ fallback }}">
+          <button type="submit" class="btn">
+            <i class="icon-heart"></i> {% if follows %}{% trans %}Don't Follow{% endtrans %}{% else %}{% trans %}Follow{% endtrans %}{% endif %}
+          </button>
+        </form>
+      </li>
+    </ul>
+    {% endif %}
   </div>
   <div class="profile-tab{% if profile.get_style() %} {{ profile.get_style() }}{% endif %}">
-  	{% block tab %}{% endblock %}
+    {% block tab %}{% endblock %}
   </div>
 </div>
 {% endblock %}