Browse Source

- Moved "readable_forums" function from threads ACL to forums manager, made it safe with special forums.
- Added extra views to private threads.

Ralfp 12 years ago
parent
commit
bc4c0c213d

+ 2 - 0
misago/acl/permissions/privatethreads.py

@@ -81,6 +81,8 @@ def cleanup(acl, perms, forums):
             if perm['can_invite_ignoring']:
                 acl.threads.acl[forum]['can_invite_ignoring'] = True
             if perm['private_threads_mod']:
+                acl.threads.acl[forum]['can_close_threads'] = True
+                acl.threads.acl[forum]['can_protect_posts'] = True
                 acl.threads.acl[forum]['can_edit_threads_posts'] = True
                 acl.threads.acl[forum]['can_move_threads_posts'] = True
                 acl.threads.acl[forum]['can_see_changelog'] = True

+ 0 - 7
misago/acl/permissions/threads.py

@@ -165,13 +165,6 @@ class ThreadsACL(BaseACL):
         if post.deleted and not (forum_role['can_delete_posts'] or (user.is_authenticated() and user == post.user)):
             raise ACLError404()
 
-    def get_readable_forums(self, acl):
-        readable = []
-        for forum in self.acl:
-            if acl.forums.can_browse(forum) and self.acl[forum]['can_read_threads']:
-                readable.append(forum)
-        return readable
-
     def filter_threads(self, request, forum, queryset):
         try:
             forum_role = self.acl[forum.pk]

+ 1 - 1
misago/apps/index.py

@@ -12,7 +12,7 @@ def index(request):
         popular_threads = cache.get('thread_ranking_%s' % request.user.make_acl_key(), 'nada')
         if popular_threads == 'nada':
             popular_threads = []
-            for thread in Thread.objects.filter(moderated=False).filter(deleted=False).filter(forum__in=request.acl.threads.get_readable_forums(request.acl)).prefetch_related('forum').order_by('-score')[:request.settings['thread_ranking_size']]:
+            for thread in Thread.objects.filter(moderated=False).filter(deleted=False).filter(forum__in=Forum.objects.readable_forums(request.acl)).prefetch_related('forum').order_by('-score')[:request.settings['thread_ranking_size']]:
                 thread.forum_name = thread.forum.name
                 thread.forum_slug = thread.forum.slug
                 popular_threads.append(thread)

+ 2 - 2
misago/apps/newsfeed.py

@@ -1,6 +1,6 @@
 from django.template import RequestContext
 from misago.decorators import block_guest
-from misago.models import Post
+from misago.models import Forum, Post
 
 @block_guest
 def newsfeed(request):
@@ -9,7 +9,7 @@ def newsfeed(request):
         follows.append(user.pk)
     queryset = []
     if follows:
-        queryset = Post.objects.filter(forum_id__in=request.acl.threads.get_readable_forums(request.acl))
+        queryset = Post.objects.filter(forum_id__in=Forum.objects.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')

+ 2 - 2
misago/apps/newthreads.py

@@ -1,11 +1,11 @@
 from datetime import timedelta
 from django.template import RequestContext
 from django.utils import timezone
-from misago.models import Thread
+from misago.models import Forum, Thread
 from misago.utils.pagination import make_pagination
 
 def new_threads(request, page=0):
-    queryset = Thread.objects.filter(forum_id__in=request.acl.threads.get_readable_forums(request.acl)).filter(deleted=False).filter(moderated=False).filter(start__gte=(timezone.now() - timedelta(days=2)))
+    queryset = Thread.objects.filter(forum_id__in=Forum.objects.readable_forums(request.acl)).filter(deleted=False).filter(moderated=False).filter(start__gte=(timezone.now() - timedelta(days=2)))
     items_total = queryset.count();
     pagination = make_pagination(page, items_total, 30)
 

+ 2 - 2
misago/apps/popularthreads.py

@@ -1,11 +1,11 @@
 from datetime import timedelta
 from django.template import RequestContext
 from django.utils import timezone
-from misago.models import Thread
+from misago.models import Forum, Thread
 from misago.utils.pagination import make_pagination
 
 def popular_threads(request, page=0):
-    queryset = Thread.objects.filter(forum_id__in=request.acl.threads.get_readable_forums(request.acl)).filter(deleted=False).filter(moderated=False)
+    queryset = Thread.objects.filter(forum_id__in=Forum.objects.readable_forums(request.acl)).filter(deleted=False).filter(moderated=False)
     items_total = queryset.count();
     pagination = make_pagination(page, items_total, 30)
 

+ 15 - 0
misago/apps/privatethreads/changelog.py

@@ -0,0 +1,15 @@
+from misago.apps.threadtype.changelog import (ChangelogChangesBaseView,
+                                              ChangelogDiffBaseView,
+                                              ChangelogRevertBaseView)
+from misago.apps.privatethreads.mixins import TypeMixin
+
+class ChangelogView(ChangelogChangesBaseView, TypeMixin):
+    pass
+
+
+class ChangelogDiffView(ChangelogDiffBaseView, TypeMixin):
+    pass
+
+
+class ChangelogRevertView(ChangelogRevertBaseView, TypeMixin):
+    pass

+ 17 - 0
misago/apps/privatethreads/delete.py

@@ -0,0 +1,17 @@
+from misago.apps.threadtype.delete import *
+from misago.apps.privatethreads.mixins import TypeMixin
+
+class DeleteThreadView(DeleteThreadBaseView, TypeMixin):
+    pass
+
+
+class HideThreadView(HideThreadBaseView, TypeMixin):
+    pass
+
+
+class DeleteReplyView(DeleteReplyBaseView, TypeMixin):
+    pass
+
+
+class HideReplyView(HideReplyBaseView, TypeMixin):
+    pass

+ 9 - 0
misago/apps/privatethreads/details.py

@@ -0,0 +1,9 @@
+from misago.apps.threadtype.details import DetailsBaseView, KarmaVotesBaseView
+from misago.apps.privatethreads.mixins import TypeMixin
+
+class DetailsView(DetailsBaseView, TypeMixin):
+    pass
+
+
+class KarmaVotesView(KarmaVotesBaseView, TypeMixin):
+    pass

+ 4 - 1
misago/apps/privatethreads/mixins.py

@@ -1,10 +1,13 @@
+from misago.acl.exceptions import ACLError404
+
 class TypeMixin(object):
     type_prefix = 'private_thread'
 
     def check_permissions(self):
         try:
             if self.thread.pk:
-                pass
+                if not self.request.user in self.thread.participants.all():
+                    raise ACLError404()
         except AttributeError:
             pass
 

+ 0 - 1
misago/apps/privatethreads/thread.py

@@ -10,7 +10,6 @@ class ThreadView(ThreadBaseView, ThreadModeration, PostsModeration, TypeMixin):
         try:
             if acl['can_move_threads_posts']:
                 actions.append(('merge', _('Merge posts into one')))
-                actions.append(('split', _('Split posts to new thread')))
             if acl['can_protect_posts']:
                 actions.append(('protect', _('Protect posts')))
                 actions.append(('unprotect', _('Remove posts protection')))

+ 8 - 4
misago/apps/privatethreads/urls.py

@@ -15,13 +15,17 @@ urlpatterns = patterns('misago.apps.privatethreads',
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/last/$', 'jumps.LastReplyView', name="private_thread_last"),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/find-(?P<post>\d+)/$', 'jumps.FindReplyView', name="private_thread_find"),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/new/$', 'jumps.NewReplyView', name="private_thread_new"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/moderated/$', 'jumps.FirstModeratedView', name="private_thread_moderated"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/reported/$', 'jumps.FirstReportedView', name="private_thread_reported"),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/show-hidden/$', 'jumps.ShowHiddenRepliesView', name="private_thread_show_hidden"),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/$', 'jumps.WatchThreadView', name="private_thread_watch"),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/watch/email/$', 'jumps.WatchEmailThreadView', name="private_thread_watch_email"),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/$', 'jumps.UnwatchThreadView', name="private_thread_unwatch"),
     url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/unwatch/email/$', 'jumps.UnwatchEmailThreadView', name="private_thread_unwatch_email"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/upvote/$', 'jumps.UpvotePostView', name="private_post_upvote"),
-    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/downvote/$', 'jumps.DownvotePostView', name="private_post_downvote"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/delete/$', 'delete.DeleteThreadView', name="private_thread_delete", kwargs={'mode': 'delete_thread'}),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/hide/$', 'delete.HideThreadView', name="private_thread_hide", kwargs={'mode': 'hide_thread'}),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/delete/$', 'delete.DeleteReplyView', name="private_post_delete", kwargs={'mode': 'delete_post'}),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/hide/$', 'delete.HideReplyView', name="private_post_hide", kwargs={'mode': 'hide_post'}),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/info/$', 'details.DetailsView', name="private_post_info"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/$', 'changelog.ChangelogView', name="private_thread_changelog"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/$', 'changelog.ChangelogDiffView', name="private_thread_changelog_diff"),
+    url(r'^thread/(?P<slug>(\w|-)+)-(?P<thread>\d+)/(?P<post>\d+)/changelog/(?P<change>\d+)/revert/$', 'changelog.ChangelogRevertView', name="private_thread_changelog_revert"),
 )

+ 2 - 1
misago/apps/profiles/posts/views.py

@@ -1,10 +1,11 @@
 from misago.apps.profiles.decorators import profile_view
 from misago.apps.profiles.template import RequestContext
+from misago.models import Forum
 from misago.utils.pagination import make_pagination
 
 @profile_view('user_posts')
 def posts(request, user, page=0):
-    queryset = user.post_set.filter(forum_id__in=request.acl.threads.get_readable_forums(request.acl)).filter(deleted=False).filter(moderated=False).select_related('thread', 'forum').order_by('-id')
+    queryset = user.post_set.filter(forum_id__in=Forum.objects.readable_forums(request.acl)).filter(deleted=False).filter(moderated=False).select_related('thread', 'forum').order_by('-id')
     count = queryset.count()
     pagination = make_pagination(page, count, 12)
     return request.theme.render_to_response('profiles/posts.html',

+ 2 - 1
misago/apps/profiles/threads/views.py

@@ -1,10 +1,11 @@
 from misago.apps.profiles.decorators import profile_view
 from misago.apps.profiles.template import RequestContext
+from misago.models import Forum
 from misago.utils.pagination import make_pagination
 
 @profile_view('user_threads')
 def threads(request, user, page=0):
-    queryset = user.thread_set.filter(forum_id__in=request.acl.threads.get_readable_forums(request.acl)).filter(deleted=False).filter(moderated=False).select_related('start_post', 'forum').order_by('-id')
+    queryset = user.thread_set.filter(forum_id__in=Forum.objects.readable_forums(request.acl)).filter(deleted=False).filter(moderated=False).select_related('start_post', 'forum').order_by('-id')
     count = queryset.count()
     pagination = make_pagination(page, count, 12)
     

+ 2 - 2
misago/apps/watchedthreads/views.py

@@ -7,13 +7,13 @@ from django.utils.translation import ugettext as _
 from misago.decorators import block_guest
 from misago.forms import Form, FormLayout, FormFields
 from misago.messages import Message
-from misago.models import WatchedThread
+from misago.models import Forum, WatchedThread
 from misago.utils.pagination import make_pagination
 
 @block_guest
 def watched_threads(request, page=0, new=False):
     # Find mode and fetch threads
-    queryset = WatchedThread.objects.filter(user=request.user).filter(forum_id__in=request.acl.threads.get_readable_forums(request.acl)).select_related('thread').filter(thread__moderated=False).filter(thread__deleted=False)
+    queryset = WatchedThread.objects.filter(user=request.user).filter(forum_id__in=Forum.objects.readable_forums(request.acl, True)).select_related('thread').filter(thread__moderated=False).filter(thread__deleted=False)
     if new:
         queryset = queryset.filter(last_read__lt=F('thread__last'))
     count = queryset.count()

+ 10 - 0
misago/models/forummodel.py

@@ -111,6 +111,16 @@ class ForumManager(models.Manager):
             for user in user.ignores.filter(id__in=check_ids).values('id'):
                 ignored_ids.append(user['id'])
 
+    def readable_forums(self, acl, include_special=False):
+        self.populate_tree()
+        readable = []
+        for forum in self.forums_tree:
+            if ((include_special or not forum.special) and 
+                    acl.forums.can_browse(forum.pk) and
+                    acl.threads.acl[forum.pk]['can_read_threads']):
+                readable.append(forum.pk)
+        return readable
+
 
 class Forum(MPTTModel):
     parent = TreeForeignKey('self', null=True, blank=True, related_name='children')

+ 81 - 0
templates/cranefly/private_threads/changelog.html

@@ -0,0 +1,81 @@
+{% extends "cranefly/layout.html" %}
+{% import "cranefly/macros.html" as macros with context %}
+
+{% block title %}{{ macros.page_title(title=(_("Post #%(post)s Changelog") % {'post': post.pk}),parent=thread.name) }}{% endblock %}
+
+{% block breadcrumb %}{{ super() }} <span class="divider"><i class="icon-chevron-right"></i></span></li>
+<li><a href="{% url 'private_threads' %}">{% trans %}Private Threads{% endtrans %}</a> <span class="divider"><i class="icon-chevron-right"></i></span></li>
+<li><a href="{% url 'private_thread' thread=thread.pk, slug=thread.slug %}">{{ thread.name }}</a> <span class="divider"><i class="icon-chevron-right"></i></span></li>
+<li class="active">{% trans post=post.pk %}Post #{{ post }} Changelog{% endtrans %}
+{%- endblock %}
+
+{% block container %}
+<div class="page-header header-primary">
+  <div class="container">
+    {{ messages_list(messages) }}
+    <ul class="breadcrumb">
+      {{ self.breadcrumb() }}</li>
+    </ul>
+    <h1>{% trans post=post.pk %}Post #{{ post }} Changelog{% endtrans %} <small>{{ thread.name }}</small></h1>
+    <ul class="unstyled header-stats">
+      <li><i class="icon-time"></i> <a href="{% url 'private_thread_find' thread=thread.pk, slug=thread.slug, post=post.pk %}">{{ post.date|reltimesince }}</a></li>
+      <li><i class="icon-user"></i> {% if post.user %}<a href="{% url 'user' user=post.user.pk, username=post.user.username_slug %}">{{ post.user.username }}</a>{% else %}{{ post.user_name }}{% endif %}</li>
+      <li><i class="icon-pencil"></i> {% trans edits=post.edits %}One edit{% pluralize %}{{ edits }} edits{% endtrans %}</li>
+      {% if post.protected %}<li><i class="icon-lock"></i> {% trans %}Protected{% endtrans %}</li>{% endif %}
+    </ul>
+  </div>
+</div>
+
+<div class="container container-primary">
+  <div class="post-changelog">
+    {% if edits %}
+    <table class="table table-striped">
+      <thead>
+        <tr>
+          <th style="width: 1%;">&nbsp;</th>
+          <th>{% trans %}Change Log{% endtrans %}</th>
+        </tr>
+      </thead>
+      <tbody>
+        {% for edit in edits %}
+        <tr>
+          <td>
+            <span class="change-{% if edit.change > 0 %}added{% elif edit.change < 0 %}removed{% else %}none{% endif %}{% if not edit.reason %} change-small{% endif %}">
+              {% if edit.change > 0 %}+{% endif %}{{ edit.change }}
+            </span>
+          </td>
+          <td>
+            <a href="{% url 'private_thread_changelog_diff' thread=thread.pk, slug=thread.slug, post=post.pk, change=edit.pk %}" class="change-no">#{{ loop.revindex }}</a>
+            {% if edit.reason %}
+            <div class="change-reason">
+              <a href="{% url 'private_thread_changelog_diff' thread=thread.pk, slug=thread.slug, post=post.pk, change=edit.pk %}">{{ edit.reason }}</a>
+            </div>{% endif %}
+            <div class="change-description">
+              <a href="{% url 'private_thread_changelog_diff' thread=thread.pk, slug=thread.slug, post=post.pk, change=edit.pk %}">
+              {% if edit.change != 0 %}{% if edit.change > 0 -%}
+              {% trans chars=edit.change %}Added one character to post.{% pluralize %}Added {{ chars }} characters to post.{% endtrans %}
+              {%- elif edit.change < 0 -%}
+              {% trans chars=edit.change|abs %}Removed one character from post.{% pluralize %}Removed {{ chars }} characters from post.{% endtrans %}
+              {%- else -%}
+              {% trans %}No change in message's length.{% endtrans %}
+              {%- endif %}{% endif %}{% if edit.thread_name_old %} {% trans old=edit.thread_name_old, new=edit.thread_name_new %}Changed thread name from "{{ old }}" to "{{ new }}".{% endtrans %}{% endif %}{% if edit.thread_name_old %} {% trans old=edit.thread_name_old, new=edit.thread_name_new %}Renamed thread from "{{ old }}" to "{{ new }}".{% endtrans %}{% endif %}</a>
+              <span class="change-details">
+                {% trans user=edit_user(edit), date=edit.date|reldate|low %}By {{ user }} {{ date }}{% endtrans %}
+              </span>
+            </div>
+          </td>
+        </tr>
+        {% endfor %}
+      </tbody>
+    </table>
+    {% else %}
+    <p class="lead">{% trans %}This post was never edited.{% endtrans %}</p>
+    {% endif %}
+  </div>
+</div>
+{% endblock %}
+
+
+{% macro edit_user(edit) -%}
+{% if edit.user_id %}<a href="{% url 'user' user=edit.user_id, username=edit.user_slug %}">{{ edit.user_name }}</a>{% else %}{{ edit.user_name }}{% endif %}
+{%- endmacro %}

+ 95 - 0
templates/cranefly/private_threads/changelog_diff.html

@@ -0,0 +1,95 @@
+{% extends "cranefly/layout.html" %}
+{% import "cranefly/macros.html" as macros with context %}
+
+{% block title %}{{ macros.page_title(title=(_("Post #%(post)s Changelog") % {'post': post.pk}),parent=thread.name) }}{% endblock %}
+
+{% block breadcrumb %}{{ super() }} <span class="divider"><i class="icon-chevron-right"></i></span></li>
+<li><a href="{% url 'private_threads' %}">{% trans %}Private Threads{% endtrans %}</a> <span class="divider"><i class="icon-chevron-right"></i></span></li>
+<li><a href="{% url 'private_thread' thread=thread.pk, slug=thread.slug %}">{{ thread.name }}</a> <span class="divider"><i class="icon-chevron-right"></i></span></li>
+<li><a href="{% url 'private_thread_changelog' thread=thread.pk, slug=thread.slug, post=post.pk %}">{% trans post=post.pk %}Post #{{ post }} Changelog{% endtrans %}</a> <span class="divider"><i class="icon-chevron-right"></i></span></li>
+<li class="active">{% trans date=change.date|reltimesince|low %}Edit from {{ date }}{% endtrans %}
+{%- endblock %}
+
+{% block container %}
+<div class="page-header header-primary">
+  <div class="container">
+    {{ messages_list(messages) }}
+    <ul class="breadcrumb">
+      {{ self.breadcrumb() }}</li>
+    </ul>
+    <h1>{% trans date=change.date|reltimesince|low %}Edit from {{ date }}{% endtrans %} <small>{% trans post=post.pk %}Post #{{ post }} Changelog{% endtrans %}</small></h1>
+    <ul class="unstyled header-stats pull-left">
+      <li><i class="icon-time"></i> <a href="{% url 'private_thread_find' thread=thread.pk, slug=thread.slug, post=post.pk %}">{{ post.date|reltimesince }}</a></li>
+      <li><i class="icon-user"></i> {% if change.user_id %}<a href="{% url 'user' user=change.user_id, username=change.user_slug %}">{{ change.user_name }}</a>{% else %}{{ change.user_name }}{% endif %}</li>
+      {% if acl.users.can_see_users_trails() %}
+      <li><i class="icon-globe"></i> {{ change.ip }}</li>
+      <li><i class="icon-qrcode"></i> {{ change.agent }}</li>
+      {% endif %}
+      {% if change.change != 0 %}<li><i class="icon-{% if change.change > 0 %}plus{% elif change.change < 0 %}minus{% endif %}"></i> {% if change.change > 0 -%}
+      {% trans chars=change.change %}Added one character{% pluralize %}Added {{ chars }} characters{% endtrans %}
+      {%- elif change.change < 0 -%}
+      {% trans chars=change.change|abs %}Removed one character{% pluralize %}Removed {{ chars }} characters{% endtrans %}
+      {%- endif %}</li>{% endif %}
+    </ul>
+  </div>
+</div>
+
+<div class="container container-primary">
+  <div class="post-diff">
+    {% if message %}
+    <div class="messages-list">
+      {{ macros.draw_message(message) }}
+    </div>
+    {% endif %}
+
+    {% if change.reason %}
+    <p class="lead">{{ change.reason }}</p>
+    {% endif %}
+
+    {% if acl.threads.can_edit_reply(user, forum, thread, post) or prev or next %}
+    <div class="diff-extra">
+      {{ pager() }}
+      {% if user.is_authenticated() and acl.threads.can_make_revert(forum, thread) %}
+      <form class="form-inline pull-right" action="{% url 'private_thread_changelog_revert' thread=thread.pk, slug=thread.slug, post=post.pk, change=change.pk %}" method="post">
+        <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+        <button type="submit" class="btn btn-danger">{% trans %}Revert this edit{% endtrans %}</button></li>
+      </form>
+      {%- endif %}
+    </div>
+    {% endif %}
+
+    <div class="post-diff-details">
+      <table>
+        <tbody>
+          {% for line in diff %}{% if line[0] != "?" %}
+          <tr>
+            <td class="line"><a href="#{{ l }}">{{ l }}.</a></td>
+            <td class="{% if line[0] == '+' %}added{% elif line[0] == '-' %}removed{% else %}stag{% endif %}{% if l is even %} even{% endif %}">{% if line[2:] %}{{ line[2:] }}{% else %}&nbsp;{% endif %}</td>
+          </tr>
+          {% set l = l + 1 %}
+          {% endif %}{% endfor %}
+        </tbody>
+      </table>
+    </div>
+
+    {% if prev or next %}
+    <div class="diff-extra">
+      {{ pager() }}
+    </div>
+    {% endif %}
+
+  </div>
+</div>
+{% endblock %}
+
+
+{% macro pager() %}
+{% if prev or prev %}
+<div class="pagination pull-left">
+  <ul>
+    {% if prev %}<li><a href="{% url 'private_thread_changelog_diff' thread=thread.pk, slug=thread.slug, post=post.pk, change=prev.pk %}"><i class="icon-chevron-left"></i> {{ prev.date|reldate }}</a></li>{% endif %}
+    {% if next %}<li><a href="{% url 'private_thread_changelog_diff' thread=thread.pk, slug=thread.slug, post=post.pk, change=next.pk %}">{{ next.date|reldate }} <i class="icon-chevron-right"></i></a></li>{% endif %}
+  </ul>
+</div>
+{% endif %}
+{% endmacro %}

+ 35 - 0
templates/cranefly/private_threads/details.html

@@ -0,0 +1,35 @@
+{% extends "cranefly/layout.html" %}
+{% import "cranefly/macros.html" as macros with context %}
+
+{% block title %}{{ macros.page_title(title=(_("Post #%(post)s Info") % {'post': post.pk}),parent=thread.name) }}{% endblock %}
+
+{% block breadcrumb %}{{ super() }} <span class="divider"><i class="icon-chevron-right"></i></span></li>
+<li><a href="{% url 'private_threads' %}">{% trans %}Private Threads{% endtrans %}</a> <span class="divider"><i class="icon-chevron-right"></i></span></li>
+<li><a href="{% url 'private_thread' thread=thread.pk, slug=thread.slug %}">{{ thread.name }}</a> <span class="divider"><i class="icon-chevron-right"></i></span></li>
+<li class="active">{% trans post=post.pk %}Post #{{ post }} Info{% endtrans %}
+{%- endblock %}
+
+{% block container %}
+<div class="page-header header-primary">
+  <div class="container">
+    {{ messages_list(messages) }}
+    <ul class="breadcrumb">
+      {{ self.breadcrumb() }}</li>
+    </ul>
+    <h1>{% trans post=post.pk %}Post #{{ post }} Info{% endtrans %} <small>{{ thread.name }}</small></h1>
+    <ul class="unstyled header-stats">
+      <li><i class="icon-time"></i> <a href="{% url 'private_thread_find' thread=thread.pk, slug=thread.slug, post=post.pk %}">{{ post.date|reltimesince }}</a></li>
+      <li><i class="icon-user"></i> {% if post.user %}<a href="{% url 'user' user=post.user.pk, username=post.user.username_slug %}">{{ post.user.username }}</a>{% else %}{{ post.user_name }}{% endif %}</li>
+      <li><i class="icon-pencil"></i> {% trans edits=post.edits %}One edit{% pluralize %}{{ edits }} edits{% endtrans %}</li>
+      {% if post.protected %}<li><i class="icon-lock"></i> {% trans %}Protected{% endtrans %}</li>{% endif %}
+    </ul>
+  </div>
+</div>
+
+<div class="container container-primary">
+  <h2>{% trans %}IP Address{% endtrans %}</h2>
+  <p class="lead">{{ post.ip }}</p>
+  <h2>{% trans %}UserAgent{% endtrans %}</h2>
+  <p class="lead">{{ post.agent }}</p>
+</div>
+{% endblock %}

+ 0 - 3
templates/cranefly/private_threads/list.html

@@ -5,9 +5,6 @@
 {% block title %}{{ macros.page_title(title=_("Private Threads"),page=pagination['page']) }}{% endblock %}
 
 {% block breadcrumb %}{{ super() }} <span class="divider"><i class="icon-chevron-right"></i></span></li>
-{% for parent in parents %}
-<li><a href="{{ parent.type|url(forum=parent.pk, slug=parent.slug) }}">{{ parent.name }}</a> <span class="divider"><i class="icon-chevron-right"></i></span></li>
-{% endfor %}
 <li class="active">{% trans %}Private Threads{% endtrans %}
 {%- endblock %}
 

+ 455 - 0
templates/cranefly/private_threads/thread.html

@@ -0,0 +1,455 @@
+{% extends "cranefly/layout.html" %}
+{% import "_forms.html" as form_theme with context %}
+{% import "cranefly/editor.html" as editor with context %}
+{% import "cranefly/macros.html" as macros with context %}
+
+{% block title %}{{ macros.page_title(title=thread.name,parent=_("Private Threads"),page=pagination['page']) }}{% endblock %}
+
+{% block breadcrumb %}{{ super() }} <span class="divider"><i class="icon-chevron-right"></i></span></li>
+<li><a href="{% url 'private_threads' %}">{% trans %}Private Threads{% endtrans %}</a> <span class="divider"><i class="icon-chevron-right"></i></span></li>
+<li class="active">{{ thread.name }}
+{%- endblock %}
+
+{% block container %}
+<div class="page-header header-primary">
+  <div class="container">
+    {{ messages_list(messages) }}
+    <ul class="breadcrumb" {{ macros.itemprop_bread() }}>
+      {{ self.breadcrumb() }}</li>
+    </ul>
+    <h1>{{ thread.name }}</h1>
+    <ul class="unstyled header-stats">
+      {% if thread.moderated %}<li><i class="icon-eye-close"></i> {% trans %}Not Reviewed{% endtrans %}</li>{% endif %}
+      <li><i class="icon-time"></i> {{ thread.last|reltimesince }}</li>
+      <li><i class="icon-user"></i> {% if thread.start_poster_id %}<a href="{% url 'user' user=thread.start_poster_id, username=thread.start_poster_slug %}">{{ thread.start_poster_name }}</a>{% else %}{{ thread.start_poster_name }}{% endif %}</li>
+      <li><i class="icon-comment"></i> {% if thread.replies > 0 -%}
+        {% trans count=thread.replies, replies=thread.replies|intcomma %}One reply{% pluralize %}{{ replies }} replies{% endtrans %}
+      {%- else -%}
+        {% trans %}No replies{% endtrans %}
+      {%- endif %}</li>
+    </ul>
+  </div>
+</div>
+
+<div class="container container-primary">
+  {% if message %}
+  <div class="messages-list">
+    {{ macros.draw_message(message) }}
+  </div>
+  {% endif %}
+
+  <div class="thread-buttons">
+    {{ pager() }} 
+    {% if acl.threads.can_reply(forum, thread) %}
+    <a href="{% url 'private_thread_reply' thread=thread.pk, slug=thread.slug %}" class="btn btn-inverse pull-right"><i class="icon-pencil"></i> {% trans %}Reply{% endtrans %}</a>
+    {% endif %}
+    {% if watcher %}
+    <form action="{% url 'private_thread_unwatch' thread=thread.pk, slug=thread.slug %}" class="form-inline pull-right" method="post"><input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}"><input type="hidden" name="retreat" value="{{ request_path }}"><button type="submit" class="btn btn-success tooltip-top" title="{% trans %}Remove thread from watched list{% endtrans %}"><i class="icon-bookmark"></i></button></form>
+    {% if watcher.email %}
+    <form action="{% url 'private_thread_unwatch_email' thread=thread.pk, slug=thread.slug %}" class="form-inline pull-right" method="post"><input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}"><input type="hidden" name="retreat" value="{{ request_path }}"><button type="submit" class="btn btn-success tooltip-top" title="{% trans %}Don't e-mail me anymore if anyone replies to this thread{% endtrans %}"><i class="icon-envelope"></i></button></form>
+    {% else %}
+    <form action="{% url 'private_thread_watch_email' thread=thread.pk, slug=thread.slug %}" class="form-inline pull-right" method="post"><input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}"><input type="hidden" name="retreat" value="{{ request_path }}"><button type="submit" class="btn tooltip-top" title="{% trans %}E-mail me if anyone replies{% endtrans %}"><i class="icon-envelope"></i></button></form>
+    {% endif %}
+    {% else %}
+    <form action="{% url 'private_thread_watch' thread=thread.pk, slug=thread.slug %}" class="form-inline pull-right" method="post"><input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}"><input type="hidden" name="retreat" value="{{ request_path }}"><button type="submit" class="btn tooltip-top" title="{% trans %}Add thread to watched list{% endtrans %}"><i class="icon-bookmark"></i></button></form>
+    <form action="{% url 'private_thread_watch_email' thread=thread.pk, slug=thread.slug %}" class="form-inline pull-right" method="post"><input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}"><input type="hidden" name="retreat" value="{{ request_path }}"><button type="submit" class="btn tooltip-top" title="{% trans %}Add thread to watched list and e-mail me if anyone replies{% endtrans %}"><i class="icon-envelope"></i></button></form>
+    {% endif %}
+    {% if ignored_posts %}
+    <form action="{% url 'private_thread_show_hidden' thread=thread.pk, slug=thread.slug %}" class="form-inline pull-right" method="post"><input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}"><button type="submit" class="btn"><i class="icon-eye-open"></i> {% trans %}Show Hidden Replies{% endtrans %}</button></form>
+    {% endif %}
+  </div>
+
+  <div class="thread-body">
+    {% for post in posts %}
+    <div id="post-{{ post.pk }}" class="post-wrapper">
+      {% if post.message %}
+      <div class="messages-list">
+        {{ macros.draw_message(post.message) }}
+      </div>
+      {% endif %}
+      {% if post.deleted and not acl.threads.can_see_deleted_posts(forum) %}
+      <div class="post-body post-muted">
+        {% if post.user_id %}
+        <a href="{% url 'user' user=post.user.pk, username=post.user.username_slug %}"><img src="{{ post.user.get_avatar(50) }}" alt="" class="user-avatar"></a>
+        {% else %}
+        <img src="{{ macros.avatar_guest(60) }}" alt="" class="user-avatar"></a>
+        {% endif %}
+        <div class="post-content">
+          <div class="post-header">
+            <div class="post-header-compact">
+              {% if post.user_id %}
+              <a href="{% url 'user' user=post.user.pk, username=post.user.username_slug %}" class="post-author">{{ post.user.username }}</a>{% if post.user.get_title() %} {{ user_label(post.user) }}{% endif %}
+              {% else %}
+              <span class="post-author">{{ post.user_name }}</span> <span class="label post-author-label post-label-guest">{% trans %}Unregistered{% endtrans %}</span>
+              {% endif %}
+              <span class="separator">&ndash;</span>
+              <a href="{% if pagination['page'] > 1 -%}
+              {% url 'private_thread' thread=thread.pk, slug=thread.slug, page=pagination['page'] %}
+              {%- else -%}
+              {% url 'private_thread' thread=thread.pk, slug=thread.slug %}
+              {%- endif %}#post-{{ post.pk }}" class="post-date">{{ post.date|reltimesince }}</a>
+              {% if post.edits %}
+              <span class="separator">&ndash;</span>
+              {% if acl.threads.can_see_changelog(user, forum, post) %}
+              <a href="{% url 'changelog' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="post-changelog tooltip-bottom" title="{% trans %}Show changelog{% endtrans %}">{% trans count=post.edits %}One edit{% pluralize %}{{ count }} edits{% endtrans %}</a>
+              {% else %}
+              <span class="post-changelog">{% trans count=post.edits %}One edit{% pluralize %}{{ count }} edits{% endtrans %}</span>
+              {% endif %}
+              {% endif %}
+            </div>
+
+            <a href="{% if pagination['page'] > 1 -%}
+            {% url 'private_thread' thread=thread.pk, slug=thread.slug, page=pagination['page'] %}
+            {%- else -%}
+            {% url 'private_thread' thread=thread.pk, slug=thread.slug %}
+            {%- endif %}#post-{{ post.pk }}" class="post-perma tooltip-left" title="{% trans %}Direct link to this post{% endtrans %}">#{{ pagination['start'] + loop.index }}</a>
+
+            {% if not post.is_read %}
+            <div class="post-extra">
+              <span class="label label-warning">
+                {% trans %}New{% endtrans %}
+              </span>
+            </div>
+            {% endif %}
+
+          </div>
+          <div class="post-message">
+            {% trans user=edit_user(post), date=post.edit_date|reltimesince|low %}{{ user }} has deleted this reply {{ date }}{% endtrans %}
+          </div>
+        </dv>
+      </div>
+      {% elif post.ignored %}
+      <div class="post-body post-muted">
+        <img src="{{ macros.avatar_guest(60) }}" alt="" class="user-avatar"></a>
+        <div class="post-arrow"></div>
+        <div class="post-content">
+          <div class="post-header">
+            <div class="post-header-compact">
+              <a href="{% if pagination['page'] > 1 -%}
+              {% url 'private_thread' thread=thread.pk, slug=thread.slug, page=pagination['page'] %}
+              {%- else -%}
+              {% url 'private_thread' thread=thread.pk, slug=thread.slug %}
+              {%- endif %}#post-{{ post.pk }}" class="post-date">{{ post.date|reltimesince }}</a>
+            </div>
+
+            <a href="{% if pagination['page'] > 1 -%}
+            {% url 'private_thread' thread=thread.pk, slug=thread.slug, page=pagination['page'] %}
+            {%- else -%}
+            {% url 'private_thread' thread=thread.pk, slug=thread.slug %}
+            {%- endif %}#post-{{ post.pk }}" class="post-perma tooltip-left" title="{% trans %}Direct link to this post{% endtrans %}">#{{ pagination['start'] + loop.index }}</a>
+
+            {% if not post.is_read %}
+            <div class="post-extra">
+              <span class="label label-warning">
+                {% trans %}New{% endtrans %}
+              </span>
+            </div>
+            {% endif %}
+
+          </div>
+          <div class="post-message">
+            {% trans %}This reply was posted by user that is on your ignored list.{% endtrans %}
+          </div>
+        </dv>
+      </div>
+      {% else %}
+      <div class="post-body">
+        {% if post.user_id %}
+        <a href="{% url 'user' user=post.user.pk, username=post.user.username_slug %}"><img src="{{ post.user.get_avatar(100) }}" alt="" class="user-avatar"></a>
+        {% else %}
+        <img src="{{ macros.avatar_guest(100) }}" alt="" class="user-avatar"></a>
+        {% endif %}
+        <div class="post-arrow"></div>
+        <div class="post-content">
+          <div class="post-header">
+            {% if post.user_id %}
+            <a href="{% url 'user' user=post.user.pk, username=post.user.username_slug %}" class="post-author">{{ post.user.username }}</a>{% if post.user.get_title() %} {{ user_label(post.user) }}{% endif %}
+            {% else %}
+            <span class="post-author">{{ post.user_name }}</span> <span class="label post-author-label post-label-guest">{% trans %}Unregistered{% endtrans %}</span>
+            {% endif %}
+            <span class="separator">&ndash;</span>
+            <a href="{% if pagination['page'] > 1 -%}
+            {% url 'private_thread' thread=thread.pk, slug=thread.slug, page=pagination['page'] %}
+            {%- else -%}
+            {% url 'private_thread' thread=thread.pk, slug=thread.slug %}
+            {%- endif %}#post-{{ post.pk }}" class="post-date">{{ post.date|reltimesince }}</a>
+            {% if post.edits %}
+            <span class="separator">&ndash;</span>
+            {% if acl.threads.can_see_changelog(user, forum, post) %}
+            <a href="{% url 'private_thread_changelog' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="post-changelog tooltip-bottom" title="{% trans %}Show changelog{% endtrans %}">{% trans count=post.edits %}One edit{% pluralize %}{{ count }} edits{% endtrans %}</a>
+            {% else %}
+            <span class="post-changelog">{% trans count=post.edits %}One edit{% pluralize %}{{ count }} edits{% endtrans %}</span>
+            {% endif %}
+            {% endif %}
+
+            {% if posts_form %}
+            <label class="checkbox post-checkbox"><input form="posts_form" name="{{ posts_form['list_items']['html_name'] }}" type="checkbox" class="checkbox-member" value="{{ post.pk }}"{% if posts_form['list_items']['has_value'] and ('' ~ post.pk) in posts_form['list_items']['value'] %} checked="checked"{% endif %}></label>
+            {% endif %}
+
+            <a href="{% if pagination['page'] > 1 -%}
+            {% url 'private_thread' thread=thread.pk, slug=thread.slug, page=pagination['page'] %}
+            {%- else -%}
+            {% url 'private_thread' thread=thread.pk, slug=thread.slug %}
+            {%- endif %}#post-{{ post.pk }}" class="post-perma tooltip-left" title="{% trans %}Direct link to this post{% endtrans %}">#{{ pagination['start'] + loop.index }}</a>
+
+            <div class="post-extra">
+              {% if post.protected and acl.threads.can_protect(forum) %}
+              <span class="label label-info">
+                {% trans %}Protected{% endtrans %}
+              </span>
+              {% endif %}
+
+              {% if post.deleted %}
+              <span class="label label-inverse">
+                {% trans %}Deleted{% endtrans %}
+              </span>
+              {% endif %}
+
+              {% if post.moderated %}
+              <span class="label label-purple">
+                {% trans %}Unreviewed{% endtrans %}
+              </span>
+              {% endif %}
+
+              {% if post.reported %}
+              <span class="label label-important">
+                {% trans %}Reported{% endtrans %}
+              </span>
+              {% endif %}
+
+              {% if not post.is_read %}
+              <span class="label label-warning">
+                {% trans %}New{% endtrans %}
+              </span>
+              {% endif %}
+            </div>
+          </div>
+          <div class="post-message">
+            <div class="markdown js-extra">
+              {{ post.post_preparsed|markdown_final|safe }}
+            </div>
+            {% if post.user.signature %}
+            <div class="post-signature">
+              <div class="markdown">
+                {{ post.user.signature_preparsed|markdown_final|safe }}
+              </div>
+            </div>
+            {% endif %}
+          </div>
+          <div class="post-footer">{% filter trim %}
+            <div class="post-actions">              
+              {% if acl.users.can_see_users_trails() -%}
+              <a href="{% url 'private_post_info' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="post-trail">{% trans %}Info{% endtrans %}</a>
+              {% endif %}
+              {% if acl.threads.can_edit_thread(user, forum, thread, post) and thread.start_post_id == post.pk %}
+              <a href="{% url 'private_thread_edit' thread=thread.pk, slug=thread.slug %}" class="post-edit">{% trans %}Edit{% endtrans %}</a>
+              {% elif acl.threads.can_edit_reply(user, forum, thread, post) %}
+              <a href="{% url 'private_post_edit' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="post-edit">{% trans %}Edit{% endtrans %}</a>
+              {%- endif %}
+              {% if acl.threads.can_reply(forum, thread) %}<a href="{% url 'private_thread_reply' thread=thread.pk, slug=thread.slug, quote=post.pk %}" class="post-reply">{% trans %}Reply{% endtrans %}</a>{% endif %}
+            </div>
+            {% if post.pk == thread.start_post_id %}
+            <div class="post-actions">
+              {% if acl.threads.can_delete_thread(user, forum, thread, post) == 2 %}
+              <form action="{% url 'private_thread_delete' thread=thread.pk, slug=thread.slug %}" class="form-inline prompt-delete-thread" method="post">
+                <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+                <span>{% trans %}Delete thread:{% endtrans %}</span>
+                <button type="submit" class="btn btn-link tooltip-top" title="{% trans %}Delete this thread for good{% endtrans %}">{% trans %}Hard{% endtrans %}</button>
+              </form>
+              {% endif %}
+              {% if not post.deleted and acl.threads.can_delete_thread(user, forum, thread, post) %}
+              <form action="{% url 'private_thread_hide' thread=thread.pk, slug=thread.slug %}" class="form-inline prompt-delete-thread" method="post">
+                <button type="submit" class="btn btn-link tooltip-top" title="{% trans %}Hide this thread from other users{% endtrans %}">{% trans %}Soft{% endtrans %}</button>
+                <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+              </form>
+              {% endif %}
+            </div>
+            {% elif post.pk != thread.start_post_id and acl.threads.can_delete_post(user, forum, thread, post) %}
+            <div class="post-actions">
+              {% if acl.threads.can_delete_post(user, forum, thread, post) == 2 -%}
+              <form action="{% url 'private_post_delete' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="form-inline prompt-delete-post" method="post">
+                <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+                <span>{% trans %}Delete reply:{% endtrans %}</span>
+                <button type="submit" class="btn btn-link tooltip-top" title="{% trans %}Delete this reply for good{% endtrans %}">{% trans %}Hard{% endtrans %}</button>
+              </form>
+              {% endif %}
+              {% if not post.deleted and acl.threads.can_delete_post(user, forum, thread, post) %}
+              <form action="{% url 'private_post_hide' thread=thread.pk, slug=thread.slug, post=post.pk %}" class="form-inline prompt-delete-post" method="post">
+                <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+                <button type="submit" class="btn btn-link tooltip-top" title="{% trans %}Hide this reply from other users{% endtrans %}">{% trans %}Soft{% endtrans %}</button>
+              </form>
+              {% endif %}
+            </div>
+            {% endif %}
+          {% endfilter %}</div>
+        </div>
+      </div>
+      {% endif %}
+    </div>
+
+    {% if post.checkpoint_set.all() %}
+    <div class="post-checkpoints">
+      {% for checkpoint in post.checkpoint_set.all() %}
+      <div class="post-checkpoint">
+        <hr>
+        <span>
+          {%- if checkpoint.action == 'limit' -%}
+          <i class="icon-lock"></i> {% trans  %}This thread has reached its post limit and has been closed.{% endtrans %}
+          {%- elif checkpoint.action == 'accepted' -%}
+          <i class="icon-ok"></i> {% trans user=checkpoint_user(checkpoint), date=checkpoint.date|reltimesince|low %}{{ user }} accepted this thread {{ date }}{% endtrans %}
+          {%- elif checkpoint.action == 'closed' -%}
+          <i class="icon-lock"></i> {% trans user=checkpoint_user(checkpoint), date=checkpoint.date|reltimesince|low %}{{ user }} closed this thread {{ date }}{% endtrans %}
+          {%- elif checkpoint.action == 'opened' -%}
+          <i class="icon-lock"></i> {% trans user=checkpoint_user(checkpoint), date=checkpoint.date|reltimesince|low %}{{ user }} opened this thread {{ date }}{% endtrans %}
+          {%- elif checkpoint.action == 'deleted' -%}
+          <i class="icon-trash"></i> {% trans user=checkpoint_user(checkpoint), date=checkpoint.date|reltimesince|low %}{{ user }} deleted this thread {{ date }}{% endtrans %}
+          {%- elif checkpoint.action == 'undeleted' -%}
+          <i class="icon-trash"></i> {% trans user=checkpoint_user(checkpoint), date=checkpoint.date|reltimesince|low %}{{ user }} restored this thread {{ date }}{% endtrans %}
+          {%- endif -%}
+        </span>
+      </div>
+      {% endfor %}
+    </div>
+    {% endif %}
+    {% endfor %}
+  </div>
+
+  {% if thread_form or posts_form %}
+  <div class="thread-moderation">
+    {% if thread_form%}
+    <form id="thread_form" class="form-inline pull-left" action="{% url 'private_thread' slug=thread.slug, thread=thread.id, page=pagination['page'] %}" method="POST">
+      <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+      <input type="hidden" name="origin" value="thread_form">
+      {{ form_theme.input_select(thread_form['thread_action'],width=3) }}
+      <button type="submit" class="btn btn-danger">{% trans %}Go{% endtrans %}</button>
+    </form>
+    {% endif %}
+    {% if posts_form%}
+    <form id="posts_form" class="form-inline pull-right" action="{% url 'private_thread' slug=thread.slug, thread=thread.id, page=pagination['page'] %}" method="POST">
+      <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+      <input type="hidden" name="origin" value="posts_form">
+      {{ form_theme.input_select(posts_form['list_action'],width=3) }}
+      <button type="submit" class="btn btn-danger">{% trans %}Go{% endtrans %}</button>
+    </form>
+    {% endif %}
+  </div>
+  {% endif %}
+
+  <div class="thread-buttons">
+    {{ pager(false) }}
+    {% if acl.threads.can_reply(forum, thread) %}
+    <a href="{% url 'private_thread_reply' thread=thread.pk, slug=thread.slug %}" class="btn btn-inverse pull-right"><i class="icon-pencil"></i> {% trans %}Reply{% endtrans %}</a>
+    {% endif %}
+  </div>
+
+  {% if acl.threads.can_reply(forum, thread) %}
+  <div class="thread-quick-reply">
+    <form action="{% url 'private_thread_reply' thread=thread.pk, slug=thread.slug %}" method="post">
+      <input type="hidden" name="{{ csrf_id }}" value="{{ csrf_token }}">
+      <input type="hidden" name="quick_reply" value="1">
+      <img src="{{ user.get_avatar(100) }}" alt="{% trans %}Your Avatar{% endtrans %}" class="user-avatar">
+      {{ editor.editor(quick_reply.post, _('Post Reply'), extra=editor_extra()) }}
+    </form>
+  </div>
+  {% endif %}
+
+</div>
+{% endblock %}
+
+{% block stylesheets %}{{ super() }}
+<link href="{{ STATIC_URL }}cranefly/highlight/styles/monokai.css" rel="stylesheet">
+{% endblock %}
+
+{% block javascripts -%}{{ super() }}
+  <script src="{{ STATIC_URL }}cranefly/highlight/highlight.pack.js"></script>
+  <script type="text/javascript">
+    hljs.tabReplace = '    ';
+    hljs.initHighlightingOnLoad();
+    EnhancePostsMD();
+    $(function () {
+      $('#thread_form').submit(function() {
+        if ($('#id_thread_action').val() == 'hard') {
+          var decision = confirm("{% trans %}Are you sure you want to delete this thread? This action is not reversible!{% endtrans %}");
+          return decision;
+        }
+        return true;
+      });
+      $('#posts_form').submit(function() {
+        if ($('.post-checkbox[]:checked').length == 0) {
+          alert("{% trans %}You have to select at least one post.{% endtrans %}");
+          return false;
+        }
+        if ($('#id_list_action').val() == 'merge') {
+          if ($('.post-checkbox[]:checked').length < 2) {
+              alert("{% trans %}You have to select at least two posts you want to merge.{% endtrans %}");
+              return false;
+          }
+          var decision = confirm("{% trans %}Are you sure you want to merge selected posts? This action is not reversible!{% endtrans %}");
+          return decision;
+        }
+        if ($('#id_list_action').val() == 'hard') {
+          var decision = confirm("{% trans %}Are you sure you want to delete selected posts? This action is not reversible!{% endtrans %}");
+          return decision;
+        }
+        return true;
+      });
+      $('.prompt-delete-thread').submit(function() {
+          var decision = confirm("{% trans %}Are you sure you want to delete this thread?{% endtrans %}");
+          return decision;
+      });
+      $('.prompt-delete-post').submit(function() {
+          var decision = confirm("{% trans %}Are you sure you want to delete this post?{% endtrans %}");
+          return decision;
+      });
+    });
+  </script>
+  {% if acl.threads.can_reply(forum, thread) %}
+  {{ editor.js() }}
+  {% endif %}
+{%- endblock %}
+
+
+{% macro user_label(user) -%}
+<{% if user.rank and user.rank.as_tab %}a href="{% url 'users' slug=user.rank.slug %}"{% else %}span{% endif %} class="label post-author-label{% if user.rank and user.rank.style %} post-label-{{ user.rank.style }}{% endif %}">{{ user.get_title() }}</{% if user.rank and user.rank.as_tab%}a{% else %}span{% endif %}>
+{%- endmacro %}
+
+
+{% macro pager(extra=true) %}
+<div class="pagination pull-left">
+  <ul>
+    {% if pagination['total'] > 0 %}
+    <li class="count">{{ macros.pager_label(pagination) }}</li>
+    {%- if pagination['prev'] > 1 %}<li><a href="{% url 'private_thread' slug=thread.slug, thread=thread.id %}" class="tooltip-top" title="{% trans %}Go to first page{% endtrans %}"><i class="icon-chevron-left"></i> {% trans %}First{% endtrans %}</a></li>{% endif -%}
+    {%- if pagination['prev'] > 0 %}<li><a href="{%- if pagination['prev'] > 1 %}{% url 'private_thread' slug=thread.slug, thread=thread.id, page=pagination['prev'] %}{% else %}{% url 'private_thread' slug=thread.slug, thread=thread.id %}{% endif %}" class="tooltip-top" title="{% trans %}Older Posts{% endtrans %}"><i class="icon-chevron-left"></i></a></li>{% endif -%}
+    {%- if pagination['next'] > 0 %}<li><a href="{% url 'private_thread' slug=thread.slug, thread=thread.id, page=pagination['next'] %}" class="tooltip-top" title="{% trans %}Newest Posts{% endtrans %}"><i class="icon-chevron-right"></i></a></li>{% endif -%}
+    {%- if pagination['next'] > 0 and pagination['next'] < pagination['total'] %}<li><a href="{% url 'private_thread' slug=thread.slug, thread=thread.id, page=pagination['total'] %}" class="tooltip-top" title="{% trans %}Go to last page{% endtrans %}">{% trans %}Last{% endtrans %} <i class="icon-chevron-right"></i></a></li>{% endif -%}
+    {% endif %}
+    {% if extra %}
+    {% if not is_read %}<li><a href="{% url 'private_thread_new' slug=thread.slug, thread=thread.id %}" class="tooltip-top" title="{% trans %}Go to first unread{% endtrans %}"><i class="icon-star"></i> {% trans %}First Unread{% endtrans %}</a></li>{% endif %}
+    {% endif %}
+  </ul>
+</div>
+{% endmacro %}
+
+
+{% macro checkpoint_user(checkpoint) -%}
+{%- if checkpoint.user_id -%}
+{{ ('<a href="' ~ 'user'|url(user=checkpoint.user_id, username=checkpoint.user_slug) ~ '">')|safe ~ (checkpoint.user_name) ~ ("</a>")|safe }}
+{%- else -%}
+<strong>{{ checkpoint.user_name }}</strong>
+{%- endif -%}
+{%- endmacro %}
+
+
+{% macro edit_user(post) -%}
+{%- if post.edit_user_id -%}
+{{ ('<a href="' ~ 'user'|url(user=post.edit_user_id, username=post.edit_user_slug) ~ '">')|safe ~ (post.edit_user_name) ~ ("</a>")|safe }}
+{%- else -%}
+<strong>{{ post.edit_user_name }}</strong>
+{%- endif -%}
+{%- endmacro %}
+
+
+{% macro editor_extra() %}
+  <button id="editor-preview" name="preview" type="submit" class="btn pull-right">{% trans %}Full Editor{% endtrans %}</button>
+{% endmacro %}