Просмотр исходного кода

Improvements in message parser and Posting action.

Ralfp 12 лет назад
Родитель
Сommit
606a07d482

+ 0 - 0
misago/markdown/extensions/__init__.py


+ 34 - 0
misago/markdown/extensions/ats.py

@@ -0,0 +1,34 @@
+import re
+import markdown
+from markdown.util import etree
+
+# Global vars
+QUOTE_AUTHOR_RE = re.compile(r'^(?P<arrows>(>|\s)+)?@(?P<username>(\w|\d)+)$')
+
+class AtsExtension(markdown.Extension):
+    def extendMarkdown(self, md):
+        md.registerExtension(self)
+        md.preprocessors.add('mi_usernames',
+                             AtsPreprocessor(md),
+                             '>mi_quote_title')
+        md.postprocessors.add('mi_usernames',
+                              AtsPostprocessor(md),
+                              '>mi_quote_title')
+
+
+class AtsPreprocessor(markdown.preprocessors.Preprocessor):
+    def __init__(self, md):
+        markdown.preprocessors.Preprocessor.__init__(self, md)
+
+    def run(self, lines):
+        clean = []
+        for l, line in enumerate(lines):
+            clean.append(line)
+        return clean
+
+
+class AtsPostprocessor(markdown.postprocessors.Postprocessor):
+    def run(self, text):
+        text = text.replace('&lt;%s:username&gt;' % self.markdown.mi_token, '<username>')
+        text = text.replace('&lt;/%s:username&gt;' % self.markdown.mi_token, '</username>')
+        return text

+ 54 - 0
misago/markdown/extensions/quotes.py

@@ -0,0 +1,54 @@
+import re
+import markdown
+from markdown.util import etree
+
+# Global vars
+QUOTE_AUTHOR_RE = re.compile(r'^(?P<arrows>(>|\s)+)?@(?P<username>(\w|\d)+)$')
+
+class QuoteTitlesExtension(markdown.Extension):
+    def extendMarkdown(self, md):
+        md.registerExtension(self)
+        md.preprocessors.add('mi_quote_title',
+                             QuoteTitlesPreprocessor(md),
+                             '>fenced_code_block')
+        md.postprocessors.add('mi_quote_title',
+                              QuoteTitlesPostprocessor(md),
+                              '_end')
+
+
+class QuoteTitlesPreprocessor(markdown.preprocessors.Preprocessor):
+    def __init__(self, md):
+        markdown.preprocessors.Preprocessor.__init__(self, md)
+
+    def run(self, lines):
+        clean = []
+        for l, line in enumerate(lines):
+            try:
+                if line.strip():
+                    at_match = QUOTE_AUTHOR_RE.match(line.strip())
+                    if at_match and lines[l + 1].strip()[0] == '>':
+                        username = '<%(token)s:quotetitle><%(token)s:username>%(name)s</%(token)s:username></%(token)s:quotetitle>' % {'token': self.markdown.mi_token, 'name': at_match.group('username')}
+                        if at_match.group('arrows'):
+                            clean.append('> %s%s' % (at_match.group('arrows'), username))
+                        else:
+                            clean.append('> %s' % username)
+                        try:
+                            self.markdown.mi_usernames.append(username)
+                        except AttributeError:
+                            self.markdown.mi_usernames = [username]
+                    else:
+                        clean.append(line)
+                else:
+                    clean.append(line)
+            except IndexError:
+                clean.append(line)
+            except Exception as e:
+                print e
+        return clean
+
+
+class QuoteTitlesPostprocessor(markdown.postprocessors.Postprocessor):
+    def run(self, text):
+        text = text.replace('&lt;%s:quotetitle&gt;' % self.markdown.mi_token, '<h3><quotetitle>')
+        text = text.replace('&lt;/%s:quotetitle&gt;' % self.markdown.mi_token, '</quotetitle></h3>')
+        return text

+ 34 - 3
misago/markdown/factory.py

@@ -1,5 +1,9 @@
-from django.conf import settings
+import re
 import markdown
+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
 
 def signature_markdown(acl, text):
     md = markdown.Markdown(
@@ -29,5 +33,32 @@ def post_markdown(request, text):
     md = markdown.Markdown(
                            safe_mode='escape',
                            output_format=settings.OUTPUT_FORMAT,
-                           extensions=['nl2br'])
-    return md.convert(text)
+                           extensions=['nl2br', 'fenced_code'])
+    md.mi_token = get_random_string(16)
+    for extension in settings.MARKDOWN_EXTENSIONS:
+        module = '.'.join(extension.split('.')[:-1])
+        extension = extension.split('.')[-1]
+        module = import_module(module)
+        attr = getattr(module, extension)
+        ext = attr()
+        ext.extendMarkdown(md)
+    text = md.convert(text)
+    # Final cleanups
+    print 'PRE-FINALISE'
+    print '------------------------------------------------'
+    print text
+    text = text.replace('<p><h3><quotetitle>', '<h3><quotetitle>')
+    text = text.replace('</quotetitle></h3></p>', '</quotetitle></h3>')
+    text = text.replace('</quotetitle></h3><br>\n', '</quotetitle></h3>\n<p>')
+    text = text.replace('\n<p></p>', '')
+    try:
+        def trans_quotetitle(match):
+            return _("Posted by %(user)s") % {'user': match.group('content')} 
+        text = re.sub(r'<quotetitle>(?P<content>.+)</quotetitle>', trans_quotetitle, text)
+        print 'REPLACED!'
+    except Exception as e:
+        print 'Kaput: %s' % e
+    print 'POST-FINALISE'
+    print '------------------------------------------------'
+    print text
+    return text

+ 7 - 0
misago/markdown/views.py

@@ -0,0 +1,7 @@
+from django.http import HttpResponse
+from misago.markdown import post_markdown
+
+def preview(request):
+    if request.POST.get('raw'):
+        return HttpResponse(post_markdown(request, request.POST.get('raw')))
+    return HttpResponse('')

+ 12 - 0
misago/settings_base.py

@@ -117,6 +117,18 @@ USERCP_EXTENSIONS = (
     'misago.usercp.blocked',
 )
 
+# List of User Profile extensions
+PROFILE_EXTENSIONS = (
+    '',
+    '',
+)
+
+# List of Markdown Extensions
+MARKDOWN_EXTENSIONS = (
+    'misago.markdown.extensions.quotes.QuoteTitlesExtension',
+    'misago.markdown.extensions.ats.AtsExtension',
+)
+
 # Name of root urls configuration
 ROOT_URLCONF = 'misago.urls'
 

+ 1 - 0
misago/threads/urls.py

@@ -11,4 +11,5 @@ urlpatterns = patterns('misago.threads.views',
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/reported/$', 'FirstReportedView', name="thread_reported"),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<page>\d+)/$', 'ThreadView', name="thread"),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/reply/$', 'PostingView', name="thread_reply", kwargs={'mode': 'new_post'}),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/reply/(?P<quote>\d+)/$', 'PostingView', name="thread_reply", kwargs={'mode': 'new_post'}),
 )

+ 13 - 0
misago/threads/views/posting.py

@@ -34,16 +34,29 @@ class PostingView(BaseView):
         self.request.acl.threads.allow_thread_view(self.request.user, self.thread)
         self.request.acl.threads.allow_reply(self.thread)
         self.parents = self.forum.get_ancestors(include_self=True).filter(level__gt=1)
+        if kwargs.get('quote'):
+            self.quote = Post.objects.select_related('user').get(pk=kwargs['quote'], thread=self.thread.pk)
         
     def get_form(self, bound=False):            
         if bound:            
             return PostForm(self.request.POST,request=self.request,mode=self.mode)
+        if self.quote:
+            quote_post = []
+            if self.quote.user:
+                quote_post.append('@%s' % self.quote.user.username)
+            else:
+                quote_post.append('@%s' % self.quote.user_name)
+            for line in self.quote.post.split('\n'):
+                quote_post.append('> %s' % line)
+            quote_post.append('\n')
+            return PostForm(request=self.request,mode=self.mode,initial={'post': '\n'.join(quote_post)})
         return PostForm(request=self.request,mode=self.mode)
             
     def __call__(self, request, **kwargs):
         self.request = request
         self.forum = None
         self.thread = None
+        self.quote = None
         self.post = None
         self.parents = None
         self.mode = kwargs.get('mode')

+ 1 - 0
misago/urls.py

@@ -14,6 +14,7 @@ urlpatterns = patterns('',
     (r'^', include('misago.threads.urls')),
     url(r'^category/(?P<slug>(\w|-)+)-(?P<forum>\d+)/$', 'misago.views.category', name="category"),
     url(r'^redirect/(?P<slug>(\w|-)+)-(?P<forum>\d+)/$', 'misago.views.redirection', name="redirect"),
+    url(r'^markdown/preview/$', 'misago.markdown.views.preview', name="md_preview"),
     url(r'^$', 'misago.views.home', name="index"),
 )
 

+ 0 - 1
misago/usercp/urls.py

@@ -1,6 +1,5 @@
 from django.conf import settings
 from django.conf.urls import include, patterns, url
-from django.utils.importlib import import_module
 
 urlpatterns = []
 for extension in settings.USERCP_EXTENSIONS:

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

@@ -979,9 +979,9 @@ td.lead-cell{color:#555555;font-weight:bold;}
 .posts-list .well-post .post-author .post-bit .lead{font-size:150%;}
 .posts-list .well-post .post-author .post-bit .user-title{color:#555555;}
 .posts-list .well-post .post-author .post-bit .post-date{margin-top:4px;color:#999999;font-size:70%;font-weight:normal;}
-.posts-list .well-post .post-content{margin-left:286px;padding:0px 16px;}.posts-list .well-post .post-content .post-perma{margin-left:8px;color:#999999;}
-.posts-list .well-post .post-content .label{margin-left:8px;padding:4px 5px;font-size:100%;}
-.posts-list .well-post .post-content .label-purple{background-color:#7a43b6;}
+.posts-list .well-post .post-content{margin-left:286px;padding:0px 16px;}.posts-list .well-post .post-content .post-extra{overflow:auto;float:right;margin-bottom:12px;}.posts-list .well-post .post-content .post-extra .post-perma{margin-left:8px;color:#999999;}
+.posts-list .well-post .post-content .post-extra .label{margin-left:8px;padding:4px 5px;font-size:100%;}
+.posts-list .well-post .post-content .post-extra .label-purple{background-color:#7a43b6;}
 .posts-list .well-post .post-content .post-foot{margin-top:20px;}.posts-list .well-post .post-content .post-foot .lead{margin:0px;color:#999999;font-size:100%;}.posts-list .well-post .post-content .post-foot .lead a{color:#999999;}
 .posts-list .well-post .post-content .post-foot .signature{border-top:1px solid #eeeeee;padding-top:12px;}.posts-list .well-post .post-content .post-foot .signature .markdown{opacity:0.7;filter:alpha(opacity=70);}
 .posts-list .well-post .post-nav{clear:both;margin-left:286px;overflow:auto;padding:8px 16px;padding-bottom:0px;margin-bottom:-8px;}.posts-list .well-post .post-nav ul{margin:0px;padding:0px;}
@@ -1002,7 +1002,11 @@ td.lead-cell{color:#555555;font-weight:bold;}
 .editor .editor-input{padding:8px;}.editor .editor-input div{margin-right:14px;}
 .editor .editor-input textarea{margin:0px;width:100%;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;}
 .editor .editor-actions{border-top:2px solid #c9c9c9;overflow:auto;padding:8px;}
-.markdown{margin-bottom:-12px;}
+.markdown{margin-bottom:-12px;}.markdown h1{font-size:160%;}
+.markdown h2{font-size:120%;}
+.markdown blockquote{background-color:#fcfcfc;border:1px solid #cccccc;border-bottom-width:2px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;display:inline-block;padding:14px 16px;width:90%;margin-left:auto;margin-right:auto;}.markdown blockquote h3{margin:0px;margin-top:-10px;padding:0px;color:#555555;font-size:100%;}
+.markdown blockquote blockquote,.markdown blockquote pre{display:block;}
+.markdown pre{display:inline-block;width:90%;margin-left:auto;margin-right:auto;}.markdown pre pre{display:block;}
 .post-content{font-size:100%;}
 .signature{font-size:90%;}
 .clickable{cursor:pointer;}

+ 40 - 1
static/sora/css/sora/markdown.less

@@ -1,7 +1,46 @@
 // Markdown styles
 // -------------------------
 .markdown {
-  margin-bottom: -12px
+  margin-bottom: -12px;
+  
+  h1 {font-size: 160%;}
+  h2 {font-size: 120%;}
+  
+  blockquote {
+    background-color: @bodyBackground;
+    border: 1px solid lighten(@grayLight, 20%);
+    border-bottom-width: 2px;
+    .border-radius(3px);
+    display: inline-block;
+    padding: 14px 16px;
+    width: 90%;
+    margin-left: auto;
+    margin-right: auto;
+    
+    h3 {
+      margin: 0px;
+      margin-top: -10px;
+      padding: 0px;
+      
+      color: @gray;
+      font-size: 100%;
+    }
+    
+    blockquote, pre {
+      display: block;
+    }
+  }
+  
+  pre {
+    display: inline-block;
+    width: 90%;
+    margin-left: auto;
+    margin-right: auto;
+    
+    pre {
+      display: block;
+    }
+  }
 }
 
 .post-content {

+ 19 - 13
static/sora/css/sora/threads.less

@@ -158,20 +158,26 @@
       margin-left: 286px;
       padding: 0px 16px;
       
-      .post-perma {
-        margin-left: 8px;
+      .post-extra {
+        overflow: auto;
+        float: right;
+        margin-bottom: 12px;
         
-        color: @grayLight;
-      }
-      
-      .label {
-        margin-left: 8px;
-        padding: 4px 5px;
-        font-size: 100%;
-      }
-      
-      .label-purple {
-        background-color: @purple;
+        .post-perma {
+          margin-left: 8px;
+          
+          color: @grayLight;
+        }
+        
+        .label {
+          margin-left: 8px;
+          padding: 4px 5px;
+          font-size: 100%;
+        }
+        
+        .label-purple {
+          background-color: @purple;
+        }
       }
       
       .post-foot {

+ 1 - 1
templates/sora/category.html

@@ -5,7 +5,7 @@
 
 {% block breadcrumb %} <span class="divider">/</span></li>
 {% for parent in parents %}
-<li class="first"><a href="{{ parent.type|url(forum=forum.pk, slug=forum.slug) }}">{{ parent.name }}</a> <span class="divider">/</span></li>
+<li><a href="{{ parent.type|url(forum=forum.pk, slug=forum.slug) }}">{{ parent.name }}</a> <span class="divider">/</span></li>
 {% endfor %}
 <li class="active">{{ category.name }}
 {%- endblock %}

+ 1 - 1
templates/sora/threads/list.html

@@ -8,7 +8,7 @@
 
 {% block breadcrumb %}{{ super() }} <span class="divider">/</span></li>
 {% for parent in parents %}
-<li class="first"><a href="{{ parent.type|url(forum=parent.pk, slug=parent.slug) }}">{{ parent.name }}</a> <span class="divider">/</span></li>
+<li><a href="{{ parent.type|url(forum=parent.pk, slug=parent.slug) }}">{{ parent.name }}</a> <span class="divider">/</span></li>
 {% endfor %}
 <li class="active">{{ forum.name }}
 {%- endblock %}

+ 78 - 7
templates/sora/threads/posting.html

@@ -5,13 +5,18 @@
 {% import "sora/editor.html" as editor with context %}
 {% import "sora/macros.html" as macros with context %}
 
-{% block title %}{{ macros.page_title(title=_("Post New Thread"), parent=forum.name) }}{% endblock %}
+{% block title %}{% if thread -%}
+{{ macros.page_title(title=_(get_title()), parent=thread.name) }}
+{%- else -%}
+{{ macros.page_title(title=_(get_title()), parent=forum.name) }}
+{%- endif %}{% endblock %}
 
 {% block breadcrumb %}{{ super() }} <span class="divider">/</span></li>
 {% for parent in parents %}
-<li class="first"><a href="{{ parent.type|url(forum=forum.pk, slug=forum.slug) }}">{{ parent.name }}</a> <span class="divider">/</span></li>
+<li><a href="{{ parent.type|url(forum=forum.pk, slug=forum.slug) }}">{{ parent.name }}</a> <span class="divider">/</span></li>
 {% endfor %}
-<li class="active">{% trans %}Post New Thread{% endtrans %}
+{% if thread %}<li><a href="{% url 'thread' thread=thread.pk, slug=thread.slug %}">{{ thread.name }}</a> <span class="divider">/</span></li>{% endif %}
+<li class="active">{{ get_title() }}
 {%- endblock %}
 
 {% block content %}
@@ -19,7 +24,7 @@
   <ul class="breadcrumb">
     {{ self.breadcrumb() }}</li>
   </ul>
-  <h1>{% trans %}Post New Thread{% endtrans %}</h1>
+  <h1>{{ get_title() }} <small>{% if thread %}{{ thread.name }}{% else %}{{ forum.name }}{% endif %}</small></h1>
   {% if thread %}
   <ul class="unstyled thread-info">
     {% if thread.moderated %}<li><i class="icon-eye-close"></i> {% trans %}Not Reviewed{% endtrans %}</li>{% endif %}
@@ -34,9 +39,75 @@
   {%- endif %}
 </div>
 {% if message %}{{ macros.draw_message(message, 'alert-form') }}{% endif %}
-<form action="{% url 'thread_new' forum=forum.pk, slug=forum.slug %}" method="post">
+<form action="{{ get_action() }}" method="post">
   <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+  {% if 'thread_name' in form.fields %}
   {{ form_theme.row_widget(form.fields.thread_name) }}
-  {{ editor.editor(form.fields.post, _('Post Thread'), rows=8) }}
+  {% endif %}
+  {{ editor.editor(form.fields.post, get_button(), rows=8) }}
 </form>
-{% endblock %}
+<div class="well well-small" id="md-border" style="display: none;">
+  <div class="markdown" id="md-preview"></div>
+</div>
+{% endblock %}
+
+{% block javascripts %}
+{{ super() }}
+    <script type="text/javascript">
+      $(function($){
+        var xhr = false;
+        preview = $('#md-preview')
+        $('#md-border').fadeIn(200);
+        $('#id_post').keyup(function() {
+          if (xhr != false) {
+            xhr.abort();
+          }
+          xhr = $.ajax({
+            type: "POST",
+            url: "{% url 'md_preview' %}",
+            data: { raw: $(this).val() },
+            success: function(data) {
+              $(preview).html(data);
+            }
+          });
+        });
+      });
+    </script>
+{% endblock %}
+
+
+{% macro get_action() -%}
+{% if mode == 'new_thread' -%}
+{% url 'thread_new' forum=forum.pk, slug=forum.slug %}
+{%- elif mode == 'edit_thread' -%}
+NADA!
+{%- elif mode in ['new_post', 'new_post_quick'] -%}
+{% url 'thread_reply' thread=thread.pk, slug=thread.slug %}
+{%- elif mode == 'edit_post' -%}
+NADA!
+{%- endif %}
+{%- endmacro %}
+
+{% macro get_title() -%}
+{% if mode == 'new_thread' -%}
+{% trans %}Post New Thread{% endtrans %}
+{%- elif mode == 'edit_thread' -%}
+{% trans %}Edit Thread{% endtrans %}
+{%- elif mode in ['new_post', 'new_post_quick'] -%}
+{% trans %}Post New Reply{% endtrans %}
+{%- elif mode == 'edit_post' -%}
+{% trans %}Edit Reply{% endtrans %}
+{%- endif %}
+{%- endmacro %}
+
+{% macro get_button() -%}
+{% if mode == 'new_thread' -%}
+{% trans %}Post Thread{% endtrans %}
+{%- elif mode == 'edit_thread' -%}
+{% trans %}Edit Thread{% endtrans %}
+{%- elif mode in ['new_post', 'new_post_quick'] -%}
+{% trans %}Post Reply{% endtrans %}
+{%- elif mode == 'edit_post' -%}
+{% trans %}Edit Reply{% endtrans %}
+{%- endif %}
+{%- endmacro %}

+ 35 - 29
templates/sora/threads/thread.html

@@ -8,7 +8,7 @@
 
 {% block breadcrumb %}{{ super() }} <span class="divider">/</span></li>
 {% for parent in parents %}
-<li class="first"><a href="{{ parent.type|url(forum=forum.pk, slug=forum.slug) }}">{{ parent.name }}</a> <span class="divider">/</span></li>
+<li><a href="{{ parent.type|url(forum=forum.pk, slug=forum.slug) }}">{{ parent.name }}</a> <span class="divider">/</span></li>
 {% endfor %}
 <li class="active">{{ thread.name }}
 {%- endblock %}
@@ -36,7 +36,7 @@
   {% if user.is_authenticated() %}
   <ul class="nav nav-pills pull-right">
     <li class="info"><a href="{% url 'thread_new' forum=forum.pk, slug=forum.slug %}"><i class="icon-ok"></i> {% trans %}Watch Thread{% endtrans %}</a></li>{% if acl.threads.can_reply(thread) %}
-    <li class="primary"><a href="{% url 'thread_new' forum=forum.pk, slug=forum.slug %}"><i class="icon-plus"></i> {% trans %}Reply{% endtrans %}</a></li>{% endif %}
+    <li class="primary"><a href="{% url 'thread_reply' thread=thread.pk, slug=thread.slug %}"><i class="icon-plus"></i> {% trans %}Reply{% endtrans %}</a></li>{% endif %}
   </ul>
   {% endif %}
 </div>
@@ -54,31 +54,33 @@
       </div>
     </div>
     <div class="post-content">
-      <a href="{% if pagination['page'] > 1 -%}
-      {% url 'thread' thread=thread.pk, slug=thread.slug, page=pagination['page'] %}
-      {%- else -%}
-      {% url 'thread' thread=thread.pk, slug=thread.slug %}
-      {%- endif %}#post-{{ post.pk }}" class="post-perma pull-right tooltip-left" title="{% trans %}Direct link to this post{% endtrans %}">#{{ pagination['start'] + loop.index }}</a>
-      {% if not post.is_read %}
-      <span class="label label-warning pull-right">
-        {% trans %}New{% endtrans %}
-      </span>
-      {% endif %}
-      {% if post.moderated %}
-      <span class="label label-purple pull-right">
-        {% trans %}Unreviewed{% endtrans %}
-      </span>
-      {% endif %}
-      {% if post.reported %}
-      <span class="label label-important pull-right">
-        {% trans %}Reported{% endtrans %}
-      </span>
-      {% endif %}
-      {% if post.deleted %}
-      <span class="label label-inverse pull-right">
-        {% trans %}Deleted{% endtrans %}
-      </span>
-      {% endif %}
+      <div class="post-extra">
+        <a href="{% if pagination['page'] > 1 -%}
+        {% url 'thread' thread=thread.pk, slug=thread.slug, page=pagination['page'] %}
+        {%- else -%}
+        {% url 'thread' thread=thread.pk, slug=thread.slug %}
+        {%- endif %}#post-{{ post.pk }}" class="post-perma pull-right tooltip-left" title="{% trans %}Direct link to this post{% endtrans %}">#{{ pagination['start'] + loop.index }}</a>
+        {% if not post.is_read %}
+        <span class="label label-warning pull-right">
+          {% trans %}New{% endtrans %}
+        </span>
+        {% endif %}
+        {% if post.moderated %}
+        <span class="label label-purple pull-right">
+          {% trans %}Unreviewed{% endtrans %}
+        </span>
+        {% endif %}
+        {% if post.reported %}
+        <span class="label label-important pull-right">
+          {% trans %}Reported{% endtrans %}
+        </span>
+        {% endif %}
+        {% if post.deleted %}
+        <span class="label label-inverse pull-right">
+          {% trans %}Deleted{% endtrans %}
+        </span>
+        {% endif %}
+      </div>
       <div class="markdown">
         {{ post.post_preparsed|safe }}
       </div>
@@ -96,12 +98,16 @@
       {% endif %}
     </div>
     <div class="post-nav">
+      {% if user.is_authenticated() %}
       <ul class="nav nav-pills pull-right">
+        {% if 1 == 2 %}
         <li><a href="#"><i class="icon-info-sign"></i> {% trans %}Info{% endtrans %}</a></li>
         <li><button class="btn danger"><i class="icon-remove"></i> {% trans %}Delete{% endtrans %}</button></li>
         <li><a href="#"><i class="icon-edit"></i> {% trans %}Edit{% endtrans %}</a></li>
-        <li><a href="#"><i class="icon-comment"></i> {% trans %}Reply{% endtrans %}</a></li>
+        {% endif %}
+        {% if acl.threads.can_reply(thread) %}<li><a href="{% url 'thread_reply' thread=thread.pk, slug=thread.slug, quote=post.pk %}"><i class="icon-comment"></i> {% trans %}Reply{% endtrans %}</a></li>{% endif %}
       </ul>
+      {% endif %}
     </div>
   </div>
   {% if post.checkpoint_set.all() %}
@@ -135,7 +141,7 @@
   {{ pager(false) }}
   {% if user.is_authenticated() and acl.threads.can_reply(thread) %}
   <ul class="nav nav-pills pull-right">
-    <li class="primary"><a href="{% url 'thread_new' forum=forum.pk, slug=forum.slug %}"><i class="icon-plus"></i> {% trans %}Reply{% endtrans %}</a></li>
+    <li class="primary"><a href="{% url 'thread_reply' thread=thread.pk, slug=thread.slug %}"><i class="icon-plus"></i> {% trans %}Reply{% endtrans %}</a></li>
   </ul>
   {% endif %}
 </div>