Browse Source

mass action controllers tweaked, mass actions for posts

Rafał Pitoń 10 years ago
parent
commit
f992ec13ef

+ 9 - 0
misago/static/misago/css/misago/dropdowns.less

@@ -188,6 +188,15 @@
 }
 
 
+// Dropup tweak
+.dropup {
+  .dropdown-menu {
+    margin-top: 0px;
+    margin-bottom: 8px;
+  }
+}
+
+
 // Utility class for js scroll handling
 .scrollable {
   overflow-y: auto;

+ 25 - 1
misago/static/misago/css/misago/posts.less

@@ -47,7 +47,7 @@
       }
     }
 
-    &.highlighted {
+    &.focus {
       .media-body {
         .panel-default {
           background: @post-active-panel-bg;
@@ -136,6 +136,30 @@
         position: relative;
         bottom: 2px;
       }
+
+      .post-check {
+        float: right;
+        margin: 0px;
+        margin-left: @line-height-computed / 2;
+
+        color: @text-muted;
+        font-size: 20px;
+        text-decoration: none;
+        line-height: 19px;
+
+        cursor: pointer;
+
+
+        &.active {
+          color: @brand-success;
+        }
+
+        input {
+          position: absolute;
+          top: -9999px;
+          left: -9999px;
+        }
+      }
     }
   }
 }

+ 2 - 2
misago/static/misago/css/misago/variables.less

@@ -319,8 +319,8 @@
 @post-panel-bg:                        @panel-bg;
 @post-panel-border:                    @panel-default-border;
 
-@post-active-panel-bg:                 lighten(#f1c40f, 48%);
-@post-active-panel-border:             lighten(#f1c40f, 40%);
+@post-active-panel-bg:                 lighten(#3498db, 44%);
+@post-active-panel-border:             lighten(#3498db, 38%);
 
 
 //== Form states and alerts

+ 63 - 6
misago/static/misago/js/misago-posts.js

@@ -1,6 +1,8 @@
 // Controller for posts lists
 $(function() {
 
+  // Post controller
+
   MisagoPost = function($element) {
 
     this.$e = $element;
@@ -9,12 +11,12 @@ $(function() {
 
     var _this = this;
 
-    this.highlight = function() {
-      this.$e.addClass('highlighted');
+    this.focus = function() {
+      this.$e.addClass('focus');
     }
 
-    this.remove_highlight = function() {
-      this.$e.removeClass('highlighted');
+    this.remove_focus = function() {
+      this.$e.removeClass('focus');
     }
 
     this.change_post = function(new_content) {
@@ -33,10 +35,10 @@ $(function() {
         Misago.Posting.load({
           api_url: _this.$e.data('edit-url'),
           on_load: function() {
-            _this.highlight();
+            _this.focus();
           },
           on_cancel: function() {
-            _this.remove_highlight();
+            _this.remove_focus();
           },
           on_post: function(data) {
             Misago.Alerts.success(data.message);
@@ -79,12 +81,16 @@ $(function() {
 
   }
 
+  // Posts controller
+
   MisagoPosts = function() {
 
     this.posts = {};
 
     var _this = this;
 
+    // discover posts data
+
     this.discover_posts = function() {
       $('.thread-post').each(function() {
         var id = $(this).data('id');
@@ -97,4 +103,55 @@ $(function() {
 
   Misago.Posts = new MisagoPosts();
 
+  // Mass actions controller
+
+  function PostsMassActions() {
+
+    var $form = $('#posts-actions');
+
+    // handle moderation form
+    var select_items_message = $('#posts-actions').data('select-items-message');
+
+    $('.post-check').each(function() {
+
+      var $check = $(this);
+
+      var $checkbox = $check.find('input');
+      if ($checkbox.prop("checked")) {
+        $check.addClass('active');
+      }
+
+      $check.on("click", function() {
+        $check.toggleClass('active');
+        if ($check.hasClass('active')) {
+          $checkbox.prop("checked", true);
+        } else {
+          $checkbox.prop("checked", false);
+        }
+        return false;
+      });
+
+    });
+
+    $form.find('li button').click(function() {
+      if ($(this).data('confirmation')) {
+        var confirmation = confirm($(this).data('confirmation'));
+        return confirmation;
+      } else {
+        return true;
+      }
+    });
+
+    $form.submit(function() {
+      if ($('.post-check.active').length == 0) {
+        alert(select_items_message);
+        return false;
+      } else {
+        return true;
+      }
+    });
+
+  }
+  PostsMassActions();
+
 });

+ 5 - 3
misago/static/misago/js/misago-threads-lists.js

@@ -1,9 +1,11 @@
-// Mass-aciton for threads
-function threadsMassActions(select_message) {
+// Mass-action for threads list
+function threadsMassActions() {
   var $form = $('#threads-actions');
   var $master = $('.master-checkbox');
   var $threads = $('.table-panel .list-group-item');
 
+  var select_items_message = $form.data('select-items-message');
+
   $form.find('li button').click(function() {
     if ($(this).data('confirmation')) {
       var confirmation = confirm($(this).data('confirmation'));
@@ -58,7 +60,7 @@ function threadsMassActions(select_message) {
 
   $form.submit(function() {
     if ($threads.filter('.active').length == 0) {
-      alert(select_message);
+      alert(select_items_message);
       return false;
     } else {
       return true;

+ 13 - 6
misago/templates/misago/thread/post.html

@@ -40,6 +40,13 @@
         </span>
         {% endif %}
 
+        {% if posts_actions %}
+        <a href="#" class="post-check">
+          <span class="fa fa-check"></span>
+          <input type="checkbox" form="posts-actions" name="item" value="{{ post.pk }}"{% if post.pk in selected_posts %}checked="checked"{% endif %}>
+        </a>
+        {% endif %}
+
         <a href="{{ post.get_absolute_url }}" class="pull-right post-date tooltip-top" title="{% trans "Link to this post" %}">
           #{{ page.start_index|add:forloop.counter0 }}
         </a>
@@ -96,32 +103,32 @@
 
         <button type="button" class="btn btn-warning btn-flat pull-right">
           <span class="fa fa-eye-slash">
-          Hide
+          {% trans "Hide" %}
         </button>
 
         <button type="button" class="btn btn-default btn-flat pull-right">
           <span class="fa fa-eye">
-          Show
+          {% trans "Show" %}
         </button>
 
         <button type="button" class="btn btn-danger btn-flat pull-right">
           <span class="fa fa-times">
-          Delete
+          {% trans "Delete" %}
         </button>
 
         <button type="button" class="btn btn-warning btn-flat pull-right">
           <span class="fa fa-exclamation-triangle">
-          Report
+          {% trans "Report" %}
         </button>
 
         <button type="button" class="btn btn-success btn-flat pull-right">
           <span class="fa fa-check">
-          Approve
+          {% trans "Approve" %}
         </button>
 
         <button type="button" class="btn btn-success btn-flat pull-left">
           <span class="fa fa-heart">
-          Like
+          {% trans "Like" %}
         </button>
       </div>
     </div>

+ 20 - 0
misago/templates/misago/thread/post_actions.html

@@ -0,0 +1,20 @@
+{% load i18n %}
+<div class="btn-group pull-right">
+  <form id="posts-actions" class="dropup" action="{{ querystring }}" method="post" data-select-items-message="{{ posts_actions.select_items_message }}">
+    {% csrf_token %}
+    <ul class="dropdown-menu scrollable" role="menu">
+      {% for action in posts_actions.get_list %}
+      <li>
+        <button type="submit" name="action" value="{{ action.action }}" class="action-{{ action.action }}" {% if action.confirmation %}data-confirmation="{{ action.confirmation }}"{% endif %}>
+          <span class="fa fa-{{ action.icon }} fa-fw"></span>
+          {{ action.name }}
+        </button>
+      </li>
+      {% endfor %}
+    </ul>
+    <button type="button" class="btn btn-default dropdown-toggle mass-controller" data-toggle="dropdown">
+      <span class="fa fa-gears"></span>
+      {% trans "With posts" %}
+    </button>
+  </form>
+</div>

+ 4 - 0
misago/templates/misago/thread/replies.html

@@ -92,6 +92,10 @@
 
       {% include "misago/thread/pagination.html" %}
 
+      {% if posts_actions %}
+      {% include "misago/thread/post_actions.html" %}
+      {% endif %}
+
     </div>
 
     <div class="reply-to-thread text-center">

+ 1 - 1
misago/templates/misago/thread/thread_actions.html

@@ -7,7 +7,7 @@
       {% trans "Thread actions" %}
     </button>
     <ul class="dropdown-menu scrollable" role="menu">
-      {% for action in thread_actions %}
+      {% for action in thread_actions.get_list %}
       <li>
         <button type="submit" name="thread_action" value="{{ action.action }}" class="action-{{ action.action }}" {% if action.confirmation %}data-confirmation="{{ action.confirmation }}"{% endif %}>
           <span class="fa fa-{{ action.icon }} fa-fw"></span>

+ 2 - 2
misago/templates/misago/threads/actions.html

@@ -3,14 +3,14 @@
   <span class="fa fa-check"></span>
 </button>
 <div class="btn-group pull-right">
-  <form id="threads-actions" action="{{ querystring }}" method="post">
+  <form id="threads-actions" action="{{ querystring }}" method="post" data-select-items-message="{{ threads_actions.select_items_message }}">
     {% csrf_token %}
     <button type="button" class="btn btn-default dropdown-toggle mass-controller" data-toggle="dropdown">
       <span class="fa fa-gears"></span>
       {% trans "With selected" %}
     </button>
     <ul class="dropdown-menu scrollable" role="menu">
-      {% for action in list_actions %}
+      {% for action in threads_actions.get_list %}
       <li>
         <button type="submit" name="action" value="{{ action.action }}" class="action-{{ action.action }}" {% if action.confirmation %}data-confirmation="{{ action.confirmation }}"{% endif %}>
           <span class="fa fa-{{ action.icon }} fa-fw"></span>

+ 1 - 1
misago/templates/misago/threads/actions_js.html

@@ -1,7 +1,7 @@
 {% load i18n %}
 <script lang="JavaScript">
   $(function() {
-    threadsMassActions("{% trans "You have to select at least one thread." %}");
+    threadsMassActions();
 
     var $threads_actions = $('#threads-actions');
 

+ 2 - 2
misago/templates/misago/threads/base.html

@@ -40,10 +40,10 @@
             </div>
             {% block thread-extra %}
             <div class="col-md-5 thread-stats">
-              {% if list_actions %}
+              {% if threads_actions %}
               <a href="#" class="thread-check">
                 <span class="fa fa-check"></span>
-                <input type="checkbox" form="threads-actions" name="thread" value="{{ thread.pk }}"{% if thread.pk in selected_threads %}checked="checked"{% endif %}>
+                <input type="checkbox" form="threads-actions" name="item" value="{{ thread.pk }}"{% if thread.pk in selected_threads %}checked="checked"{% endif %}>
               </a>
               {% endif %}
 

+ 2 - 2
misago/templates/misago/threads/forum.html

@@ -54,7 +54,7 @@
     {% include "misago/threads/show.html" %}
   {% endif %}
 
-  {% if user.is_authenticated and list_actions %}
+  {% if threads_actions %}
     {% include "misago/threads/actions.html" %}
   {% endif %}
 
@@ -96,7 +96,7 @@
 {% block javascripts %}
 {{ block.super }}
 {% include "misago/forums/js.html" %}
-{% if user.is_authenticated and list_actions %}
+{% if threads_actions %}
   {% include "misago/threads/actions_js.html" %}
 {% endif %}
 {% if forum.acl.can_start_threads %}

+ 0 - 90
misago/templates/misago/threads/merge.html

@@ -1,90 +0,0 @@
-{% extends "misago/threads/base.html" %}
-{% load i18n misago_forms %}
-
-
-{% block title %}{% trans "Merge threads" %} | {{ block.super }}{% endblock title %}
-
-
-{% block content %}
-<div{% if forum.css %} class="page-{{ forum.css_class }}"{% endif %}>
-  <div class="page-header">
-    <div class="container">
-      {% if path %}
-      <ol class="breadcrumb">
-        {% for crumb in path|slice:":-1" %}
-        <li>
-          <a href="{{ crumb.get_absolute_url }}">{{ crumb }}</a><span class="fa fa-chevron-right"></span>
-        </li>
-        {% endfor %}
-        <li>
-          <a href="{{ forum.get_absolute_url }}">{{ forum }}</a>
-        </li>
-      </ol>
-      {% endif %}
-
-      <h1>{% trans "Merge threads" %}</h1>
-    </div>
-  </div>
-
-  <div class="container">
-    <form method="POST">
-      {% csrf_token %}
-      <input type="hidden" name="action" value="merge">
-      {% for thread in threads %}
-      <input type="hidden" name="thread" value="{{ thread.pk }}">
-      {% endfor %}
-
-      <div class="row">
-        <div class="col-md-8 col-md-offset-2">
-
-          <div class="form-panel">
-
-            <div class="form-header">
-              <h2>
-                {% blocktrans trimmed with forum=forum %}
-                  Merge threads in {{ forum }}
-                {% endblocktrans %}
-              </h2>
-            </div>
-
-            {% include "misago/form_errors.html" %}
-            <div class="form-body no-fieldsets">
-
-              <div class="form-group">
-                <label class="control-label">{% trans "Threads that will be merged:" %}</label>
-                <div class="form-control-static">
-                  <ul class="list-unstyled">
-                    {% for thread in threads %}
-                    <li>
-                      {% if thread.is_pinned %}
-                      <span class="fa fa-star-o fa-fw"></span>
-                      {% else %}
-                      <span class="fa fa-circle-o fa-fw"></span>
-                      {% endif %}
-                      <a href="{{ thread.get_absolute_url }}" class="item-title">{{ thread }}</a>
-                    </li>
-                    {% endfor %}
-                  </ul>
-                </div>
-              </div>
-
-              {% form_row form.merged_thread_title %}
-
-            </div>
-
-            <div class="form-footer text-center">
-
-              <button class="btn btn-primary" name="submit">{% trans "Merge threads" %}</button>
-              <a href="" class="btn btn-default">{% trans "Cancel" %}</a>
-
-            </div>
-          </div>
-
-        </div>
-      </div><!-- /.row -->
-
-    </form>
-  </div>
-
-</div>
-{% endblock content %}

+ 1 - 1
misago/templates/misago/threads/merge/full.html

@@ -31,7 +31,7 @@
       {% csrf_token %}
       <input type="hidden" name="action" value="merge">
       {% for thread in threads %}
-      <input type="hidden" name="thread" value="{{ thread.pk }}">
+      <input type="hidden" name="item" value="{{ thread.pk }}">
       {% endfor %}
 
       <div class="row">

+ 1 - 1
misago/templates/misago/threads/merge/modal.html

@@ -3,7 +3,7 @@
   {% csrf_token %}
   <input type="hidden" name="action" value="merge">
   {% for thread in threads %}
-  <input type="hidden" name="thread" value="{{ thread.pk }}">
+  <input type="hidden" name="item" value="{{ thread.pk }}">
   {% endfor %}
   <div class="modal-header">
     <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>

+ 0 - 90
misago/templates/misago/threads/move.html

@@ -1,90 +0,0 @@
-{% extends "misago/threads/base.html" %}
-{% load i18n misago_forms %}
-
-
-{% block title %}{% trans "Move threads" %} | {{ block.super }}{% endblock title %}
-
-
-{% block content %}
-<div{% if forum.css %} class="page-{{ forum.css_class }}"{% endif %}>
-  <div class="page-header">
-    <div class="container">
-      {% if path %}
-      <ol class="breadcrumb">
-        {% for crumb in path|slice:":-1" %}
-        <li>
-          <a href="{{ crumb.get_absolute_url }}">{{ crumb }}</a><span class="fa fa-chevron-right"></span>
-        </li>
-        {% endfor %}
-        <li>
-          <a href="{{ forum.get_absolute_url }}">{{ forum }}</a>
-        </li>
-      </ol>
-      {% endif %}
-
-      <h1>{% trans "Move threads" %}</h1>
-    </div>
-  </div>
-
-  <div class="container">
-    <form method="POST">
-      {% csrf_token %}
-      <input type="hidden" name="action" value="move">
-      {% for thread in threads %}
-      <input type="hidden" name="thread" value="{{ thread.pk }}">
-      {% endfor %}
-
-      <div class="row">
-        <div class="col-md-8 col-md-offset-2">
-
-          <div class="form-panel">
-
-            <div class="form-header">
-              <h2>
-                {% blocktrans trimmed with forum=forum %}
-                  Move threads from {{ forum }}
-                {% endblocktrans %}
-              </h2>
-            </div>
-
-            {% include "misago/form_errors.html" %}
-            <div class="form-body no-fieldsets">
-
-              <div class="form-group">
-                <label class="control-label">{% trans "Threads that will be moved:" %}</label>
-                <div class="form-control-static">
-                  <ul class="list-unstyled">
-                    {% for thread in threads %}
-                    <li>
-                      {% if thread.is_pinned %}
-                      <span class="fa fa-star-o fa-fw"></span>
-                      {% else %}
-                      <span class="fa fa-circle-o fa-fw"></span>
-                      {% endif %}
-                      <a href="{{ thread.get_absolute_url }}" class="item-title">{{ thread }}</a>
-                    </li>
-                    {% endfor %}
-                  </ul>
-                </div>
-              </div>
-
-              {% form_row form.new_forum %}
-
-            </div>
-
-            <div class="form-footer text-center">
-
-              <button class="btn btn-primary" name="submit">{% trans "Move threads" %}</button>
-              <a href="" class="btn btn-default">{% trans "Cancel" %}</a>
-
-            </div>
-          </div>
-
-        </div>
-      </div><!-- /.row -->
-
-    </form>
-  </div>
-
-</div>
-{% endblock content %}

+ 1 - 1
misago/templates/misago/threads/move/full.html

@@ -31,7 +31,7 @@
       {% csrf_token %}
       <input type="hidden" name="action" value="move">
       {% for thread in threads %}
-      <input type="hidden" name="thread" value="{{ thread.pk }}">
+      <input type="hidden" name="item" value="{{ thread.pk }}">
       {% endfor %}
 
       <div class="row">

+ 1 - 1
misago/templates/misago/threads/move/modal.html

@@ -3,7 +3,7 @@
   {% csrf_token %}
   <input type="hidden" name="action" value="move">
   {% for thread in threads %}
-  <input type="hidden" name="thread" value="{{ thread.pk }}">
+  <input type="hidden" name="item" value="{{ thread.pk }}">
   {% endfor %}
   <div class="modal-header">
     <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>

+ 29 - 29
misago/threads/tests/test_forumthreads_view.py

@@ -798,7 +798,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # label threads with invalid label
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'label:mehssiah', 'thread': [t.pk for t in threads]
+            'action': 'label:mehssiah', 'item': [t.pk for t in threads]
         })
         self.assertEqual(response.status_code, 200)
         self.assertIn("Requested action is invalid.", response.content)
@@ -807,7 +807,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
             'action': 'label:%s' % labels[0].slug,
-            'thread': [t.pk for t in threads]
+            'item': [t.pk for t in threads]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -820,7 +820,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
             'action': 'label:%s' % labels[0].slug,
-            'thread': [t.pk for t in threads]
+            'item': [t.pk for t in threads]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -833,7 +833,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
             'action': 'label:%s' % labels[1].slug,
-            'thread': [t.pk for t in threads]
+            'item': [t.pk for t in threads]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -845,7 +845,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # remove labels from threads
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'unlabel', 'thread': [t.pk for t in threads]
+            'action': 'unlabel', 'item': [t.pk for t in threads]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -857,7 +857,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # remove labels from unlabeled threads
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'unlabel', 'thread': [t.pk for t in threads]
+            'action': 'unlabel', 'item': [t.pk for t in threads]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -893,7 +893,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # pin pinned
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'pin', 'thread': [pinned.pk]
+            'action': 'pin', 'item': [pinned.pk]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -906,7 +906,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # pin unpinned
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'pin', 'thread': [pinned.pk, thread.pk]
+            'action': 'pin', 'item': [pinned.pk, thread.pk]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -925,7 +925,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # unpin thread
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'unpin', 'thread': [thread.pk]
+            'action': 'unpin', 'item': [thread.pk]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -960,7 +960,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # approve approved thread
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'approve', 'thread': [thread.pk]
+            'action': 'approve', 'item': [thread.pk]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -972,7 +972,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # approve moderated thread
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'approve', 'thread': [moderated_thread.pk]
+            'action': 'approve', 'item': [moderated_thread.pk]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -1005,7 +1005,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # see move threads form
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'move', 'thread': [t.pk for t in threads[:5]]
+            'action': 'move', 'item': [t.pk for t in threads[:5]]
         })
         self.assertEqual(response.status_code, 200)
 
@@ -1016,7 +1016,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
             'action': 'move',
-            'thread': [t.pk for t in threads[:5]],
+            'item': [t.pk for t in threads[:5]],
             'submit': '',
             'new_forum': new_forum.pk + 1234
         })
@@ -1029,7 +1029,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         category = Forum.objects.all_forums().filter(role="category")[:1][0]
         response = self.client.post(self.link, data={
             'action': 'move',
-            'thread': [t.pk for t in threads[:5]],
+            'item': [t.pk for t in threads[:5]],
             'submit': '',
             'new_forum': category.pk
         })
@@ -1043,7 +1043,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         redirect = Forum.objects.all_forums().filter(role="redirect")[:1][0]
         response = self.client.post(self.link, data={
             'action': 'move',
-            'thread': [t.pk for t in threads[:5]],
+            'item': [t.pk for t in threads[:5]],
             'submit': '',
             'new_forum': redirect.pk
         })
@@ -1056,7 +1056,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.override_acl(test_acl, new_forum)
         response = self.client.post(self.link, data={
             'action': 'move',
-            'thread': [t.pk for t in threads[:5]],
+            'item': [t.pk for t in threads[:5]],
             'submit': '',
             'new_forum': new_forum.pk
         })
@@ -1092,7 +1092,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # see merge threads form
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'merge', 'thread': [t.pk for t in threads[:5]]
+            'action': 'merge', 'item': [t.pk for t in threads[:5]]
         })
         self.assertEqual(response.status_code, 200)
 
@@ -1100,7 +1100,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
             'action': 'merge',
-            'thread': [t.pk for t in threads[:5]],
+            'item': [t.pk for t in threads[:5]],
             'submit': ''
         })
         self.assertEqual(response.status_code, 200)
@@ -1111,7 +1111,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
             'action': 'merge',
-            'thread': [threads[0].pk],
+            'item': [threads[0].pk],
             'submit': ''
         })
         self.assertEqual(response.status_code, 200)
@@ -1122,7 +1122,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
             'action': 'merge',
-            'thread': [t.pk for t in threads[:5]],
+            'item': [t.pk for t in threads[:5]],
             'merged_thread_title': 'Merged thread',
             'submit': ''
         })
@@ -1162,7 +1162,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # close threads
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'close', 'thread': [t.pk for t in threads]
+            'action': 'close', 'item': [t.pk for t in threads]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -1174,7 +1174,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # close closed threads
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'close', 'thread': [t.pk for t in threads]
+            'action': 'close', 'item': [t.pk for t in threads]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -1186,7 +1186,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # open closed threads
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'open', 'thread': [t.pk for t in threads]
+            'action': 'open', 'item': [t.pk for t in threads]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -1198,7 +1198,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # open opened threads
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'open', 'thread': [t.pk for t in threads]
+            'action': 'open', 'item': [t.pk for t in threads]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -1227,7 +1227,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # hide threads
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'hide', 'thread': [t.pk for t in threads]
+            'action': 'hide', 'item': [t.pk for t in threads]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -1239,7 +1239,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # hide hidden threads
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'hide', 'thread': [t.pk for t in threads]
+            'action': 'hide', 'item': [t.pk for t in threads]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -1251,7 +1251,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # unhide hidden threads
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'unhide', 'thread': [t.pk for t in threads]
+            'action': 'unhide', 'item': [t.pk for t in threads]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -1263,7 +1263,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         # unhide visible threads
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'unhide', 'thread': [t.pk for t in threads]
+            'action': 'unhide', 'item': [t.pk for t in threads]
         })
         self.assertEqual(response.status_code, 302)
 
@@ -1293,7 +1293,7 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
 
         self.override_acl(test_acl)
         response = self.client.post(self.link, data={
-            'action': 'delete', 'thread': [t.pk for t in threads]
+            'action': 'delete', 'item': [t.pk for t in threads]
         })
 
         forum = Forum.objects.get(pk=self.forum.pk)

+ 4 - 1
misago/threads/views/generic/actions.py

@@ -21,6 +21,9 @@ class ActionsBase(object):
             self.available_actions = []
         self.selected_ids = []
 
+    def __nonzero__(self):
+        return bool(self.available_actions)
+
     def get_available_actions(self, kwargs):
         raise NotImplementedError("get_available_actions has to return list "
                                   "of dicts with allowed actions")
@@ -71,7 +74,7 @@ class ActionsBase(object):
     def handle_mass_action(self, request, queryset):
         action, action_arg = self.resolve_action(request)
         self.selected_ids = self.clean_selection(
-            request.POST.getlist('thread', []))
+            request.POST.getlist('item', []))
 
         filtered_queryset = queryset.filter(pk__in=self.selected_ids)
         if filtered_queryset.exists():

+ 1 - 1
misago/threads/views/generic/forum/view.py

@@ -70,7 +70,7 @@ class ForumView(ThreadsView):
             'page': threads.page,
             'paginator': threads.paginator,
 
-            'list_actions': actions.get_list(),
+            'threads_actions': actions,
             'selected_threads': actions.get_selected_ids(),
 
             'sorting': sorting,

+ 35 - 1
misago/threads/views/generic/thread/postsactions.py

@@ -13,4 +13,38 @@ class PostsActions(ActionsBase):
     is_mass_action = True
 
     def get_available_actions(self, kwargs):
-        return []
+        self.thread = kwargs['thread']
+        self.forum = self.thread.forum
+
+        actions = []
+
+        if self.forum.acl['can_hide_posts']:
+            actions.append({
+                'action': 'unhide',
+                'icon': 'eye',
+                'name': _("Unhide posts")
+            })
+            actions.append({
+                'action': 'hide',
+                'icon': 'eye-slash',
+                'name': _("Hide posts")
+            })
+        if self.forum.acl['can_hide_posts'] == 2:
+            actions.append({
+                'action': 'delete',
+                'icon': 'times',
+                'name': _("Delete posts"),
+                'confirmation': _("Are you sure you want to delete selected "
+                                  "posts? This action can't be undone.")
+            })
+
+        return actions
+
+    def action_unhide(self, request, posts):
+        pass
+
+    def action_hide(self, request, posts):
+        pass
+
+    def action_delete(self, request, posts):
+        pass

+ 9 - 3
misago/threads/views/generic/thread/view.py

@@ -71,13 +71,18 @@ class ThreadView(ViewBase):
         thread_actions = self.ThreadActions(user=request.user, thread=thread)
         posts_actions = self.PostsActions(user=request.user, thread=thread)
 
+        page, posts = self.get_posts(request.user, forum, thread, kwargs)
+
         if request.method == 'POST':
             if thread_actions.query_key in request.POST:
                 response = thread_actions.handle_post(request, thread)
                 if response:
                     return response
+            if posts_actions.query_key in request.POST:
+                response = posts_actions.handle_post(request, posts)
+                if response:
+                    return response
 
-        page, posts = self.get_posts(request.user, forum, thread, kwargs)
         threadstracker.make_posts_read_aware(request.user, thread, posts)
         threadstracker.read_thread(request.user, thread, posts[-1])
 
@@ -97,11 +102,12 @@ class ThreadView(ViewBase):
             'path': get_forum_path(forum),
 
             'thread': thread,
-            'thread_actions': thread_actions.get_list(),
+            'thread_actions': thread_actions,
             'thread_reply_message': thread_reply_message,
 
             'posts': posts,
-            'posts_actions': posts_actions.get_list(),
+            'posts_actions': posts_actions,
+            'selected_posts': posts_actions.get_selected_ids(),
 
             'paginator': page.paginator,
             'page': page,

+ 2 - 1
misago/threads/views/generic/threads/view.py

@@ -47,6 +47,7 @@ class ThreadsView(ViewBase):
         if self.Filtering:
             filtering.filter(threads)
 
+        actions = None
         if self.Actions:
             actions = self.Actions(user=request.user)
             if request.method == 'POST':
@@ -74,7 +75,7 @@ class ThreadsView(ViewBase):
 
         if self.Actions:
             context.update({
-                'list_actions': actions.get_list(),
+                'threads_actions': actions,
                 'selected_threads': actions.get_selected_ids(),
             })