Rafał Pitoń 10 лет назад
Родитель
Сommit
35469eb20b

+ 1 - 0
misago/conf/defaults.py

@@ -78,6 +78,7 @@ PIPELINE_JS = {
             'misago/js/misago-posting.js',
             'misago/js/misago-posting-participants.js',
             'misago/js/misago-posts.js',
+            'misago/js/misago-thread-participants.js',
         ),
         'output_filename': 'misago.js',
     },

+ 22 - 15
misago/static/misago/css/misago/header.less

@@ -172,24 +172,31 @@
 
       color: @text-muted;
 
-      img {
-      	border-radius: @border-radius-small;
-      	position: relative;
-      	bottom: 1px;
-      }
+      &>li {
+        &>img {
+          border-radius: @border-radius-small;
+          position: relative;
+          bottom: 1px;
+        }
 
-      a:link, a:visited {
-        color: @text-muted;
-      }
+        &>button {
+          background: none;
+          border: none;
+        }
 
-      a:hover {
-        color: @state-hover;
-        text-decoration: none;
-      }
+        &>a:link, &>a:visited, &>button {
+          color: @text-muted;
+        }
+
+        &>a:hover, &>button:hover {
+          color: @state-hover;
+          text-decoration: none;
+        }
 
-      a:active {
-        color: @state-clicked;
-        text-decoration: none;
+        &>a:active, &>button:active {
+          color: @state-clicked;
+          text-decoration: none;
+        }
       }
     }
   }

+ 10 - 2
misago/static/misago/js/misago-modal.js

@@ -23,17 +23,25 @@
       }
     }
 
-    this.post = function(url, data) {
+    this.post = function(url, data, on_load) {
       $.post(url, data, function(data) {
         _this.show(data);
         Misago.DOM.changed();
+
+        if (on_load !== undefined) {
+          on_load(data)
+        }
       });
     }
 
-    this.get = function(url) {
+    this.get = function(url, on_load) {
       $.get(url, function(data) {
         _this.show(data);
         Misago.DOM.changed();
+
+        if (on_load !== undefined) {
+          on_load(data)
+        }
       });
     }
   };

+ 16 - 0
misago/static/misago/js/misago-thread-participants.js

@@ -0,0 +1,16 @@
+MisagoThreadParticipants = function() {
+
+    var _this = this;
+
+    this.open = function(options) {
+
+      console.log(options)
+
+    }
+
+}
+
+
+$(function() {
+  Misago.ParticipantsEditor = new MisagoThreadParticipants();
+});

+ 70 - 0
misago/templates/misago/privatethreads/thread.html

@@ -0,0 +1,70 @@
+{% extends "misago/thread/replies.html" %}
+{% load i18n %}
+
+
+{% block page-details %}
+{{ block.super }}
+<li>
+  <button type="button" class="btn-show-participants" data-participants-url="{% url 'misago:private_thread_participants' thread_slug=thread.slug thread_id=thread.id %}">
+    <span class="fa fa-users"></span> {% blocktrans trimmed count users=thread.participants_list|length %}
+      {{ users }} participant
+    {% plural %}
+      {{ users }} participants
+    {% endblocktrans %}
+  </button>
+<li>
+{% endblock page-details %}
+
+
+{% block thread-actions %}
+{{ block.super }}
+
+{% if 'participants' in thread_actions %}
+<button class="btn action-participants btn-default pull-right" type="button">
+  <span class="fa fa-users"></span>
+  {% trans "Edit participants" %}
+</button>
+{% endif %}
+{% endblock thread-actions %}
+
+
+{% block javascripts %}
+{{ block.super }}
+<script lang="JavaScript">
+  $(function() {
+    var participants_cache = {};
+
+    function enable_participants_popover($e, data) {
+      $e.popover({
+        html: true,
+        content: data,
+        placement: 'bottom',
+        trigger: 'focus',
+        template: '<div class="popover" role="tooltip"><div class="arrow"></div><div class="popover-content"></div></div>'
+      });
+    }
+
+    $('.btn-show-participants').click(function() {
+      var $btn = $(this);
+      var api_url = $btn.data('participants-url');
+
+      if (participants_cache[api_url] == undefined) {
+        $.get(api_url, function(data) {
+          participants_cache[api_url] = data;
+          enable_participants_popover($btn, participants_cache[api_url]);
+          $btn.popover('show');
+        })
+      }
+
+    });
+
+    {% if 'participants' in thread_actions %}
+    $('.action-participants').click(function() {
+      Misago.ParticipantsEditor.open({
+        api_url: 'kitties!'
+      })
+    });
+    {% endif %}
+  });
+</script>
+{% endblock %}

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

@@ -30,6 +30,7 @@
       <h1 id="thread-title">{{ thread }}</h1>
 
       <ul class="list-inline page-details">
+        {% block page-details %}
         <li class="tooltip-bottom" title="{% trans "Thread author" %}">
           {% if thread.starter_id %}
             <a href="{% url USER_PROFILE_URL user_slug=thread.starter_slug user_id=thread.starter_id %}">
@@ -52,6 +53,7 @@
             {{ thread.last_post_on|date }}
           </abbr>
         </li>
+        {% endblock page-details %}
       </ul>
 
     </div>
@@ -60,6 +62,7 @@
 
     <div class="table-actions">
 
+      {% block thread-nav %}
       {% include "misago/thread/pagination.html" %}
 
       {% if thread.acl.can_review and thread.has_moderated_posts %}
@@ -75,10 +78,13 @@
         {% trans "Reported posts" %}
       </a>
       {% endif %}
+      {% endblock thread-nav %}
 
+      {% block thread-actions %}
       {% if thread_actions %}
       {% include "misago/thread/thread_actions.html" %}
       {% endif %}
+      {% endblock thread-actions %}
 
     </div>
 
@@ -90,11 +96,15 @@
 
     <div class="table-actions">
 
+      {% block thread-nav-down %}
       {% include "misago/thread/pagination.html" %}
+      {% endblock thread-nav-down %}
 
+      {% block post-actions %}
       {% if posts_actions %}
       {% include "misago/thread/posts_actions.html" %}
       {% endif %}
+      {% endblock post-actions %}
 
     </div>
 

+ 18 - 0
misago/threads/tests/test_privatethread_view.py

@@ -77,3 +77,21 @@ class PrivateThreadTests(AuthenticatedUserTestCase):
         response = self.client.get(self.thread.get_absolute_url())
         self.assertEqual(response.status_code, 200)
         self.assertIn(self.thread.title, response.content)
+
+    def test_moderator_can_takeover_reported_thread(self):
+        """moderator can take over private thread"""
+        override_acl(self.user, {
+            'can_use_private_threads': True,
+            'can_moderate_private_threads': True
+        })
+
+        self.thread.has_reported_posts = True
+        self.thread.save()
+
+        response = self.client.post(self.thread.get_absolute_url(), data={
+            'thread_action': 'takeover'
+        })
+        self.assertEqual(response.status_code, 302)
+
+        user = self.thread.threadparticipant_set.get(user=self.user)
+        self.assertTrue(user.is_owner)

+ 82 - 7
misago/threads/views/privatethreads.py

@@ -1,3 +1,4 @@
+from django.contrib import messages
 from django.http import Http404
 from django.shortcuts import get_object_or_404, render
 from django.utils.translation import ugettext as _
@@ -98,9 +99,6 @@ class PrivateThreads(generic.Threads):
         threads_qs = Forum.objects.private_threads().thread_set
         return exclude_invisible_private_threads(threads_qs, self.user)
 
-    def clean_threads_activity(self, user, threads):
-        pass
-
 
 class PrivateThreadsFiltering(generic.ThreadsFiltering):
     def get_available_filters(self):
@@ -145,15 +143,92 @@ class ThreadParticipantsView(PrivateThreadsMixin, generic.ViewBase):
         })
 
 
-"""
-Generics
-"""
+class PrivateThreadActions(generic.ThreadActions):
+    def get_available_actions(self, kwargs):
+        user = kwargs['user']
+        thread = kwargs['thread']
+
+        is_moderator = user.acl['can_moderate_private_threads']
+        if thread.participant and thread.participant.is_owner:
+            is_owner = True
+        else:
+            is_owner = False
+
+        actions = []
+
+        if is_moderator and not is_owner:
+            actions.append({
+                'action': 'takeover',
+                'icon': 'level-up',
+                'name': _("Takeover thread")
+            })
+
+        if is_owner:
+            actions.append({
+                'action': 'participants',
+                'icon': 'users',
+                'name': _("Edit participants"),
+                'is_button': True
+            })
+
+        if is_moderator:
+            if thread.is_closed:
+                actions.append({
+                    'action': 'open',
+                    'icon': 'unlock-alt',
+                    'name': _("Open thread")
+                })
+            else:
+                actions.append({
+                    'action': 'close',
+                    'icon': 'lock',
+                    'name': _("Close thread")
+                })
+
+            actions.append({
+                'action': 'delete',
+                'icon': 'times',
+                'name': _("Delete thread"),
+                'confirmation': _("Are you sure you want to delete this "
+                                  "thread? This action can't be undone.")
+            })
+
+        return actions
+
+    def action_takeover(self, request, thread):
+        ThreadParticipant.objects.set_owner(thread, request.user)
+        messages.success(request, _("You are now owner of this thread."))
+
+
 @private_threads_view
 class ThreadView(PrivateThreadsMixin, generic.ThreadView):
-    pass
+    template = 'misago/privatethreads/thread.html'
+    ThreadActions = PrivateThreadActions
 
 
 @private_threads_view
+class EditThreadParticipantsView(PrivateThreadsMixin, generic.ViewBase):
+    template = 'misago/privatethreads/participants_modal.html'
+
+    def dispatch(self, request, *args, **kwargs):
+        thread = self.get_thread(request, **kwargs)
+
+        if not request.is_ajax():
+            response = render(request, 'misago/errorpages/wrong_way.html')
+            response.status_code = 405
+            return response
+
+        participants_qs = thread.threadparticipant_set
+        participants_qs = participants_qs.select_related('user')
+
+        return self.render(request, {
+            'participants': participants_qs.order_by('-is_owner', 'user__slug')
+        })
+
+"""
+Generics
+"""
+@private_threads_view
 class GotoLastView(PrivateThreadsMixin, generic.GotoLastView):
     pass