Browse Source

Fix #434: modals for move/merge actions

Rafał Pitoń 10 years ago
parent
commit
1098ba60db

+ 1 - 0
misago/conf/defaults.py

@@ -63,6 +63,7 @@ PIPELINE_JS = {
             'misago/js/misago-yesnoswitch.js',
             'misago/js/misago-alerts.js',
             'misago/js/misago-ajax.js',
+            'misago/js/misago-modal.js',
             'misago/js/misago-scrolling.js',
             'misago/js/misago-uiserver.js',
             'misago/js/misago-notifications.js',

+ 12 - 0
misago/static/misago/css/misago/modals.less

@@ -18,9 +18,21 @@
         }
       }
 
+      .modal-form {
+        padding-bottom: @line-height-computed / 5;
+      }
+
       .modal-footer {
         background: @modal-footer-bg-color;
         border-radius: 0px 0px @border-radius-large @border-radius-large;
+
+        &.text-center {
+          text-align: center;
+
+          &>* {
+            float: none;
+          }
+        }
       }
     }
   }

+ 50 - 0
misago/static/misago/js/misago-modal.js

@@ -0,0 +1,50 @@
+// Misago modal extension
+(function($) {
+
+  // Modal handler class definition
+  // ===============================
+
+  var MisagoModal = function() {
+
+    this.$modal = $('#ajax-modal');
+    this.$content = $('#ajax-modal .modal-content');
+
+    this.is_visible = function() {
+      return $('body').hasClass('modal-open');
+    }
+
+    this.show_modal = function(html) {
+      if (this.is_visible()) {
+        this.$content.fadeOut(200);
+        this.$content.html(html);
+        this.$content.fadeIn(200);
+      } else {
+        this.$content.html(html);
+        this.$modal.modal({show: true});
+      }
+    }
+
+    this.post = function(url, data) {
+      var _this = this;
+      $.post(url, data, function(data) {
+        _this.show_modal(data);
+        $.misago_dom().changed();
+      });
+    }
+
+    // Return object
+    return this;
+
+  };
+
+  // Plugin definition
+  // ==========================
+
+  $.misago_modal = function(options) {
+    if ($._misago_modal == undefined) {
+      $._misago_modal = MisagoModal();
+    }
+    return $._misago_modal;
+  };
+
+}(jQuery));

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

@@ -29,6 +29,8 @@
 
     {% include "misago/footer.html" %}
 
+    {% include "misago/modal.html" %}
+
     {# We include JavaScript at the end of page body so it renders faster #}
     <script lang="JavaScript">
       var is_authenticated = {{ user.is_authenticated|yesno:"true,false" }};

+ 7 - 0
misago/templates/misago/modal.html

@@ -0,0 +1,7 @@
+<!-- Ajax Modal -->
+<div class="modal fade" id="ajax-modal" tabindex="-1" role="dialog" aria-labelledby="ajaxModalLabel" aria-hidden="true">
+  <div class="modal-dialog">
+    <div class="modal-content">
+    </div>
+  </div>
+</div>

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

@@ -12,7 +12,7 @@
     <ul class="dropdown-menu scrollable" role="menu">
       {% for action in list_actions %}
       <li>
-        <button type="submit" name="action" value="{{ action.action }}" {% if action.confirmation %}data-confirmation="{{ action.confirmation }}"{% endif %}>
+        <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>

+ 14 - 0
misago/templates/misago/threads/actions_js.html

@@ -2,5 +2,19 @@
 <script lang="JavaScript">
   $(function() {
     threadsMassActions("{% trans "You have to select at least one thread." %}");
+
+    var $threads_actions = $('#threads-actions');
+
+    $('#threads-actions .action-move').click(function() {
+      var action_data = $threads_actions.serialize($threads_actions) + '&action=move';
+      $.misago_modal().post('', action_data);
+      return false;
+    });
+
+    $('#threads-actions .action-merge').click(function() {
+      var action_data = $threads_actions.serialize($threads_actions) + '&action=merge';
+      $.misago_modal().post('', action_data);
+      return false;
+    });
   });
 </script>

+ 20 - 0
misago/templates/misago/threads/merge/body.html

@@ -0,0 +1,20 @@
+{% load i18n misago_forms %}
+<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 %}

+ 72 - 0
misago/templates/misago/threads/merge/full.html

@@ -0,0 +1,72 @@
+{% extends "misago/threads/base.html" %}
+{% load i18n %}
+
+
+{% 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.name }}</a><span class="fa fa-chevron-right"></span>
+        </li>
+        {% endfor %}
+        <li>
+          <a href="{{ forum.get_absolute_url }}">{{ forum.name }}</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.name %}
+                  Merge threads in {{ forum }}
+                {% endblocktrans %}
+              </h2>
+            </div>
+
+            {% include "misago/form_errors.html" %}
+            <div class="form-body no-fieldsets">
+
+              {% include "misago/threads/merge/body.html" %}
+
+            </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 %}

+ 24 - 0
misago/templates/misago/threads/merge/modal.html

@@ -0,0 +1,24 @@
+{% load i18n %}
+<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="modal-header">
+    <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+    <h4 class="modal-title" id="ajaxModalLabel">
+      <span class="fa fa-reply-all fa-fw"></span>
+      {% trans "Merge threads" %}
+    </h4>
+  </div>
+  <div class="modal-body modal-form">
+
+    {% include "misago/threads/merge/body.html" %}
+
+  </div>
+  <div class="modal-footer text-center">
+    <button class="btn btn-primary" name="submit">{% trans "Merge threads" %}</button>
+    <button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel" %}</button>
+  </div>
+</form>

+ 20 - 0
misago/templates/misago/threads/move/body.html

@@ -0,0 +1,20 @@
+{% load i18n misago_forms %}
+<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 %}

+ 72 - 0
misago/templates/misago/threads/move/full.html

@@ -0,0 +1,72 @@
+{% extends "misago/threads/base.html" %}
+{% load i18n %}
+
+
+{% 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.name }}</a><span class="fa fa-chevron-right"></span>
+        </li>
+        {% endfor %}
+        <li>
+          <a href="{{ forum.get_absolute_url }}">{{ forum.name }}</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.name %}
+                  Move threads from {{ forum }}
+                {% endblocktrans %}
+              </h2>
+            </div>
+
+            {% include "misago/form_errors.html" %}
+            <div class="form-body no-fieldsets">
+
+              {% include "misago/threads/move/body.html" %}
+
+            </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 %}

+ 24 - 0
misago/templates/misago/threads/move/modal.html

@@ -0,0 +1,24 @@
+{% load i18n %}
+<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="modal-header">
+    <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span><span class="sr-only">Close</span></button>
+    <h4 class="modal-title" id="ajaxModalLabel">
+      <span class="fa fa-arrow-right fa-fw"></span>
+      {% trans "Move threads" %}
+    </h4>
+  </div>
+  <div class="modal-body modal-form">
+
+    {% include "misago/threads/move/body.html" %}
+
+  </div>
+  <div class="modal-footer text-center">
+    <button class="btn btn-primary" name="submit">{% trans "Move threads" %}</button>
+    <button type="button" class="btn btn-default" data-dismiss="modal">{% trans "Cancel" %}</button>
+  </div>
+</form>

+ 2 - 1
misago/threads/tests/test_forumthreads_view.py

@@ -1065,7 +1065,8 @@ class ForumThreadsViewTests(AuthenticatedUserTestCase):
         self.override_acl(test_acl)
         response = self.client.get(self.link)
         self.assertEqual(response.status_code, 200)
-        self.assertIn("5 threads were moved to New Forum.", response.content)
+        self.assertIn("5 threads were moved to &quot;New Forum&quot;.",
+                      response.content)
 
         for thread in new_forum.thread_set.all():
             self.assertIn(thread, threads[:5])

+ 21 - 9
misago/threads/views/generic/forum/actions.py

@@ -108,7 +108,7 @@ class ForumActions(Actions):
             if label.slug == label_slug:
                 break
         else:
-            raise moderation.ModerationError(_("Requested action is invalid."))
+            raise moderation.ModerationError(self.invalid_action_message)
 
         changed_threads = 0
         for thread in threads:
@@ -176,7 +176,8 @@ class ForumActions(Actions):
             message = _("No threads were unpinned.")
             messages.info(request, message)
 
-    move_threads_template = 'misago/threads/move.html'
+    move_threads_full_template = 'misago/threads/move/full.html'
+    move_threads_modal_template = 'misago/threads/move/modal.html'
 
     def action_move(self, request, threads):
         form = MoveThreadsForm(acl=request.user.acl, forum=self.forum)
@@ -197,8 +198,8 @@ class ForumActions(Actions):
 
                 changed_threads = len(threads)
                 message = ungettext(
-                    '%(changed)d thread was moved to %(forum)s.',
-                    '%(changed)d threads were moved to %(forum)s.',
+                    '%(changed)d thread was moved to "%(forum)s".',
+                    '%(changed)d threads were moved to "%(forum)s".',
                 changed_threads)
                 messages.success(request, message % {
                     'changed': changed_threads,
@@ -207,14 +208,20 @@ class ForumActions(Actions):
 
                 return None # trigger threads list refresh
 
-        return render(request, self.move_threads_template, {
+        if request.is_ajax():
+            template = self.move_threads_modal_template
+        else:
+            template = self.move_threads_full_template
+
+        return render(request, template, {
             'form': form,
             'forum': self.forum,
             'path': get_forum_path(self.forum),
             'threads': threads
         })
 
-    merge_threads_template = 'misago/threads/merge.html'
+    merge_threads_full_template = 'misago/threads/merge/full.html'
+    merge_threads_modal_template = 'misago/threads/merge/modal.html'
 
     def action_merge(self, request, threads):
         if len(threads) == 1:
@@ -254,8 +261,8 @@ class ForumActions(Actions):
 
                 changed_threads = len(threads)
                 message = ungettext(
-                    '%(changed)d thread was merged into %(thread)s.',
-                    '%(changed)d threads were merged into %(thread)s.',
+                    '%(changed)d thread was merged into "%(thread)s".',
+                    '%(changed)d threads were merged into "%(thread)s".',
                 changed_threads)
                 messages.success(request, message % {
                     'changed': changed_threads,
@@ -264,7 +271,12 @@ class ForumActions(Actions):
 
                 return None # trigger threads list refresh
 
-        return render(request, self.merge_threads_template, {
+        if request.is_ajax():
+            template = self.merge_threads_modal_template
+        else:
+            template = self.merge_threads_full_template
+
+        return render(request, template, {
             'form': form,
             'forum': self.forum,
             'path': get_forum_path(self.forum),

+ 11 - 3
misago/threads/views/generic/threads/actions.py

@@ -2,6 +2,8 @@ from django.contrib import messages
 from django.shortcuts import redirect
 from django.utils.translation import ungettext, ugettext_lazy, ugettext as _
 
+from misago.core.exceptions import AjaxError
+
 from misago.threads import moderation
 from misago.threads.moderation import ModerationError
 
@@ -10,6 +12,7 @@ __all__ = ['Actions']
 
 
 class Actions(object):
+    invalid_action_message = ugettext_lazy("Requested action is invalid.")
     select_threads_message = ugettext_lazy(
         "You have to select at least one thread.")
 
@@ -39,7 +42,7 @@ class Actions(object):
                 action_callable = 'action_%s' % action_name
                 return getattr(self, action_callable), action_arg
         else:
-            raise ModerationError(_("Requested action is invalid."))
+            raise ModerationError(self.invalid_action_message)
 
     def clean_selection(self, data):
         filtered_data = []
@@ -68,14 +71,19 @@ class Actions(object):
                     response = action(request, filtered_queryset)
                 if response:
                     return response
+                elif request.is_ajax():
+                    raise AjaxError(self.invalid_action_message, 406)
                 else:
                     # prepare default response: page reload
                     return redirect(request.path)
             else:
                 raise ModerationError(self.select_threads_message)
         except ModerationError as e:
-            messages.error(request, e.message)
-            return False
+            if request.is_ajax():
+                raise AjaxError(e.message, 406)
+            else:
+                messages.error(request, e.message)
+                return False
 
     def get_list(self):
         return self.available_actions