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

+ 1 - 0
misago/conf/defaults.py

@@ -75,6 +75,7 @@ PIPELINE_JS = {
             'misago/js/misago-threads-lists.js',
             'misago/js/misago-onebox.js',
             'misago/js/misago-posting.js',
+            'misago/js/misago-posts.js',
         ),
         'output_filename': 'misago.js',
     },

+ 28 - 8
misago/static/misago/css/misago/posts.less

@@ -46,6 +46,23 @@
         }
       }
     }
+
+    &.highlighted {
+      .media-body {
+        .panel-default {
+          background: @post-active-panel-bg;
+          border-color: @post-active-panel-border;
+
+          &:after {
+            border-right-color: @post-active-panel-bg;
+          }
+
+          &:before {
+            border-right-color: @post-active-panel-border;
+          }
+        }
+      }
+    }
   }
 }
 
@@ -116,29 +133,31 @@
       }
 
       .label {
-      	position: relative;
-      	bottom: 2px;
+        position: relative;
+        bottom: 2px;
       }
     }
   }
 }
 
+
 // Post alert
 //
 //==
 .posts-list {
   .panel {
-  	.alert {
-  		border: none;
-  		border-radius: 0px;
+    .alert {
+      border: none;
+      border-radius: 0px;
       margin-top: @line-height-computed / 2;
       margin-bottom: 0;
 
-  		text-align: center;
-  	}
+      text-align: center;
+    }
   }
 }
 
+
 // Post body
 //
 //==
@@ -152,6 +171,7 @@
   }
 }
 
+
 // Post footer
 //
 //==
@@ -160,7 +180,7 @@
     .panel-footer {
       background: none;
       border-top: none;
-      margin-top: @line-height-computed * -0.5;
+      margin-top: @line-height-computed * -1.25;
     }
   }
 }

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

@@ -316,8 +316,11 @@
 @user-card-active-shadow:              @state-clicked;
 
 //** Post panels
-@post-panel-bg:               @panel-bg;
-@post-panel-border:           @panel-default-border;
+@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%);
 
 
 //== Form states and alerts

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

@@ -32,3 +32,12 @@
     }
   }
 }
+
+
+.posts-list {
+  .post.rank-team {
+    .user-rank {
+      background-color: fadeOut(@brand-accent, 10%);
+    }
+  }
+}

+ 42 - 19
misago/static/misago/js/misago-posting.js

@@ -12,6 +12,9 @@ $(function() {
 
     if (this.$markup.text() == '') {
       this.$markup.hide();
+    } else {
+      this.$message.hide();
+      Misago.Onebox.activate(this.$markup);
     }
 
     this.api_url = options.api_url;
@@ -106,6 +109,7 @@ $(function() {
       this.affix_end = 0;
 
       this.on_cancel = null;
+      this.on_post = null;
 
     }
 
@@ -129,6 +133,12 @@ $(function() {
         this.on_cancel = null;
       }
 
+      if (options.on_post !== undefined) {
+        this.on_post = options.on_post
+      } else {
+        this.on_post = null;
+      }
+
       this.$ajax_loader = this.$container.find('.ajax-loader');
       this.$ajax_complete = this.$container.find('.ajax-complete');
 
@@ -150,13 +160,22 @@ $(function() {
             if (data.post_url !== undefined) {
               _this.posted = true;
               _this.$ajax_loader.hide();
-              _this.$ajax_complete.addClass('in')
 
-              if (data.post_url.indexOf(window.location.pathname) != -1) {
-                window.location.href = data.post_url;
-                window.location.reload(true)
-              } else {
-                window.location.href = data.post_url;
+              var on_post = true;
+
+              if (_this.on_post != null) {
+                on_post = _this.on_post(data);
+              }
+
+              if (on_post) {
+                _this.$ajax_complete.addClass('in')
+
+                if (data.post_url.indexOf(window.location.pathname) != -1) {
+                  window.location.href = data.post_url;
+                  window.location.reload(true)
+                } else {
+                  window.location.href = data.post_url;
+                }
               }
             } else if (data.errors !== undefined) {
               Misago.Alerts.error(data.errors[0]);
@@ -174,17 +193,7 @@ $(function() {
       })
 
       this.$container.find('button[name="cancel"]').click(function() {
-
-        if (_this.has_content()) {
-          var decision = confirm(lang_dismiss_editor);
-          if (decision) {
-            _this.cancel();
-          }
-        } else {
-          _this.cancel();
-        }
-
-        return false;
+        _this.cancel();
       });
 
       return true;
@@ -214,6 +223,13 @@ $(function() {
 
       if (this.$form !== null) {
 
+        if (this.has_content() && !this.posted) {
+          var decision = confirm(lang_dismiss_editor);
+          if (!decision) {
+            return false;
+          }
+        }
+
         if (this.$preview !== null) {
           this.$preview.stop();
         }
@@ -223,13 +239,20 @@ $(function() {
           $('.main-footer').show();
         });
 
-        if (this.on_cancel !== undefined) {
+        if (this.on_cancel !== null) {
           this.on_cancel();
         }
 
         this._clear();
+        return true;
       }
 
+      return false;
+
+    }
+
+    this.is_open = function() {
+      return this.$form !== null;
     }
 
     this.has_content = function() {
@@ -249,7 +272,7 @@ $(function() {
   Misago.Posting = new MisagoPosting();
 
   $(window).on("beforeunload", function() {
-    if (Misago.Posting.has_content() && !Misago.Posting.posted) {
+    if (Misago.Posting.is_open() && Misago.Posting.has_content() && !Misago.Posting.posted) {
       return lang_dismiss_editor;
     }
   })

+ 72 - 0
misago/static/misago/js/misago-posts.js

@@ -0,0 +1,72 @@
+// Controller for posts lists
+$(function() {
+
+  MisagoPost = function($element) {
+
+    this.$e = $element;
+    this.$content = this.$e.find('.post-body');
+    this.id = this.$e.data('id');
+
+    var _this = this;
+
+    this.highlight = function() {
+      $element.addClass('highlighted');
+    }
+
+    this.remove_highlight = function() {
+      $element.removeClass('highlighted');
+    }
+
+    this.change_post = function(new_content) {
+      this.$content.fadeTo('fast', 0.1, function() {
+        _this.$content.html(new_content);
+        Misago.Onebox.activate(_this.$content);
+        Misago.DOM.changed();
+
+        _this.$content.fadeTo('fast', 1);
+      });
+    }
+
+    this.$e.find('.btn-edit').click(function() {
+
+      if (!Misago.Posting.is_open() || Misago.Posting.cancel()) {
+        Misago.Posting.load({
+          api_url: _this.$e.data('edit-url'),
+          on_load: function() {
+            _this.highlight();
+          },
+          on_cancel: function() {
+            _this.remove_highlight();
+          },
+          on_post: function(data) {
+            Misago.Alerts.success(data.message);
+            _this.change_post(data.parsed);
+            Misago.Posting.cancel();
+            return false;
+          }
+        });
+      }
+
+    });
+
+  }
+
+  MisagoPosts = function() {
+
+    this.posts = {};
+
+    var _this = this;
+
+    this.discover_posts = function() {
+      $('.thread-post').each(function() {
+        var id = $(this).data('id');
+        _this.posts[id] = new MisagoPost($(this));
+      });
+    }
+    this.discover_posts();
+
+  }
+
+  Misago.Posts = new MisagoPosts();
+
+});

+ 2 - 2
misago/templates/misago/posting/replyform.html

@@ -1,4 +1,4 @@
-{% load i18n misago_editor misago_forms %}
+{% load i18n misago_editor misago_forms misago_shorthands %}
 {% include 'misago/form_errors.html' %}
 
 {% if form.title %}
@@ -32,7 +32,7 @@
         <p class="lead empty-message">
           {% trans "Once you start writing mesage, it's preview will be displayed here." %}
         </p>
-        <article class="misago-markup"></article>
+        <article class="misago-markup">{{ post.is_valid|iftrue:post.parsed|safe }}</article>
       </div>
       <p class="preview-footer text-muted small">
         <span class="fa fa-refresh fa-fw"></span>

+ 15 - 14
misago/templates/misago/thread/post.html

@@ -1,5 +1,5 @@
 {% load i18n misago_avatars %}
-<div id="post-{{ post.pk }}" class="media post">
+<div id="post-{{ post.pk }}" class="media post thread-post{% if post.poster_id and post.poster.rank.css_class %} rank-{{ post.poster.rank.css_class }}{% endif %}" data-id="{{ post.pk }}" data-edit-url="{{ post.get_edit_url }}"=>
   {% if post.poster %}
   <a class="user-avatar pull-left" href="{% url USER_PROFILE_URL user_slug=post.poster.slug user_id=post.poster.id %}">
     <img class="media-object" src="{{ post.poster|avatar:100 }}" alt="{% trans "Poster avatar" %}">
@@ -17,6 +17,9 @@
         {% include "misago/user_state.html" with user=post.poster state=post.poster.online_state %}
         <a class="user-name" href="{% url USER_PROFILE_URL user_slug=post.poster.slug user_id=post.poster.id %}">
           {{ post.poster }}</a>
+        {% if post.poster.short_title %}
+        <span class="label user-rank">{{ post.poster.short_title }}</span>
+        {% endif %}
         {% else %}
         <span class="fa fa-power-off fa-fw user-offline tooltip-top" title="{% blocktrans with user=post.poster_name %}{{ user }}'s forum account has been deleted.{% endblocktrans %}"></span>
         <span class="user-name">
@@ -61,34 +64,32 @@
       </div>
       {% endif %}
 
+      {% if post.is_valid %}
       <div class="panel-body">
-
-        {% if post.is_valid %}
         <article class="post-body misago-markup">
           {{ post.parsed|safe }}
         <article>
-        {% else %}
-        <p class="lead corrupted-message">
-          <span class="fa fa-exclamation-triangle"></span>
-          {% trans "Post can't be displayed due to invalid message checksum." %}
-        </p>
-        {% endif %}
-
       </div>
+      {% else %}
+      <div class="alert alert-danger">
+        <span class="fa fa-exclamation-triangle fa-fw fa-lg"></span>
+        {% trans "Post can't be displayed due to invalid message checksum." %}
+      </div>
+      {% endif %}
 
       <div class="panel-footer">
 
         {% if thread.acl.can_reply %}
-        <button type="button" class="btn btn-primary btn-flat pull-right">
+        <button type="button" class="btn btn-reply btn-primary btn-flat pull-right">
           <span class="fa fa-reply">
-          Reply
+          {% trans "Reply" %}
         </button>
         {% endif %}
 
         {% if post.acl.can_edit %}
-        <button type="button" class="btn btn-default btn-flat pull-right">
+        <button type="button" class="btn btn-edit btn-default btn-flat pull-right">
           <span class="fa fa-pencil">
-          Edit
+          {% trans "Edit" %}
         </button>
         {% endif %}
 

+ 7 - 0
misago/threads/models/post.py

@@ -86,6 +86,13 @@ class Post(models.Model):
             'post_id': self.id
         })
 
+    def get_edit_url(self):
+        return reverse('misago:edit_post', kwargs={
+            'forum_id': self.forum_id,
+            'thread_id': self.thread_id,
+            'post_id': self.id
+        })
+
     @property
     def short(self):
         if self.is_valid:

+ 7 - 5
misago/threads/permissions.py

@@ -293,7 +293,7 @@ def add_acl_to_thread(user, thread):
         'can_close': forum_acl.get('can_close_threads'),
         'can_move': forum_acl.get('can_move_threads'),
         'can_review': forum_acl.get('can_review_moderated_content'),
-        'can_report': forum_acl.get('can_report'),
+        'can_report': forum_acl.get('can_report_content'),
         'can_see_reports': forum_acl.get('can_see_reports')
     })
 
@@ -302,7 +302,8 @@ def add_acl_to_thread(user, thread):
             can_change_label = forum_acl.get('can_change_threads_labels') == 1
             thread.acl['can_change_label'] = can_change_label
         if not thread.acl['can_hide']:
-            thread.acl['can_hide'] = forum_acl.get('can_hide_own_threads')
+            if not thread.replies:
+                thread.acl['can_hide'] = forum_acl.get('can_hide_own_threads')
 
 
 def add_acl_to_post(user, post):
@@ -314,9 +315,10 @@ def add_acl_to_post(user, post):
         'can_unhide': forum_acl.get('can_hide_threads'),
         'can_hide': forum_acl.get('can_hide_threads'),
         'can_delete': forum_acl.get('can_hide_threads'),
-        'can_protect': can_reply_thread(user, post.thread),
-        'can_report': can_reply_thread(user, post.thread),
-        'can_approve': can_reply_thread(user, post.thread),
+        'can_protect': forum_acl.get('can_protect_posts'),
+        'can_report': forum_acl.get('can_report_content'),
+        'can_see_reports': forum_acl.get('can_see_reports'),
+        'can_approve': forum_acl.get('can_review_moderated_content'),
     })
 
 

+ 1 - 1
misago/threads/posting/recordedit.py

@@ -9,7 +9,7 @@ class RecordEditMiddleware(PostingMiddleware):
 
         if self.mode == EDIT:
             self.original_title = self.thread.title
-            self.original_post = self.post.raw
+            self.original_post = self.post.original
 
     def save(self, form):
         if self.mode == EDIT:

+ 21 - 11
misago/threads/posting/reply.py

@@ -2,6 +2,7 @@ from misago.markup import Editor
 
 from misago.threads.checksums import update_post_checksum
 from misago.threads.forms.posting import ReplyForm, ThreadForm
+from misago.threads.permissions import can_edit_thread
 from misago.threads.posting import PostingMiddleware, START, REPLY, EDIT
 
 
@@ -9,16 +10,26 @@ class ReplyFormMiddleware(PostingMiddleware):
     def make_form(self):
         initial_data = {'title': self.thread.title, 'post': self.post.original}
 
-        if self.mode == START:
+        if self.mode == EDIT:
+            if can_edit_thread(self.user, self.thread):
+                FormType = ThreadForm
+            else:
+                FormType = FormType
+        elif self.mode == START:
+            FormType = ThreadForm
+        else:
+            FormType = FormType
+
+        if FormType == ThreadForm:
             if self.request.method == 'POST':
                 form = ThreadForm(self.thread, self.post, self.request.POST)
             else:
                 form = ThreadForm(self.thread, self.post, initial=initial_data)
         else:
             if self.request.method == 'POST':
-                form = ReplyForm(self.post, self.request.POST)
+                form = FormType(self.post, self.request.POST)
             else:
-                form = ReplyForm(self.post, initial=initial_data)
+                form = FormType(self.post, initial=initial_data)
 
         form.post_editor = Editor(form['post'])
         return form
@@ -32,14 +43,12 @@ class ReplyFormMiddleware(PostingMiddleware):
             self.new_thread(form)
 
         if self.mode == EDIT:
-            self.edit_post()
+            self.edit_post(form)
         else:
             self.new_post()
 
         self.post.updated_on = self.datetime
-
-        if self.mode != EDIT:
-            self.post.save()# We need post id for checksum
+        self.post.save()
 
         update_post_checksum(self.post)
         self.post.update_fields.append('checksum')
@@ -54,10 +63,11 @@ class ReplyFormMiddleware(PostingMiddleware):
         self.thread.last_post_on = self.datetime
         self.thread.save()
 
-    def edit_post(self):
-        self.post.last_editor_name = self.user
-        self.post.poster_name = self.user.username
-        self.post.poster_slug = self.user.slug
+    def edit_post(self, form):
+        if form.cleaned_data.get('title'):
+            self.thread.set_title(form.cleaned_data['title'])
+            self.thread.update_fields.extend(('title', 'slug'))
+        self.post.last_editor_name = self.user.username
 
     def new_post(self):
         self.post.thread = self.thread

+ 4 - 3
misago/threads/urls.py

@@ -33,10 +33,11 @@ urlpatterns += patterns('',
 )
 
 
-from misago.threads.views.threads import StartThreadView, ReplyView, EditView
+from misago.threads.views.posting import PostingView
 urlpatterns += patterns('',
-    url(r'^start-thread/(?P<forum_id>\d+)/$', StartThreadView.as_view(), name='start_thread'),
-    url(r'^reply-thread/(?P<forum_id>\d+)/(?P<thread_id>\d+)/$', ReplyView.as_view(), name='reply_thread'),
+    url(r'^start-thread/(?P<forum_id>\d+)/$', PostingView.as_view(), name='start_thread'),
+    url(r'^reply-thread/(?P<forum_id>\d+)/(?P<thread_id>\d+)/$', PostingView.as_view(), name='reply_thread'),
+    url(r'^edit-post/(?P<forum_id>\d+)/(?P<thread_id>\d+)/(?P<post_id>\d+)/edit/$', PostingView.as_view(), name='edit_post'),
 )
 
 

+ 0 - 1
misago/threads/views/generic/__init__.py

@@ -3,7 +3,6 @@ from misago.threads.views.generic.base import *
 from misago.threads.views.generic.goto import *
 from misago.threads.views.generic.gotopostslist import *
 from misago.threads.views.generic.post import *
-from misago.threads.views.generic.posting import *
 from misago.threads.views.generic.thread import *
 from misago.threads.views.generic.threads import *
 from misago.threads.views.generic.forum import *

+ 32 - 9
misago/threads/views/generic/base.py

@@ -46,6 +46,7 @@ class ThreadMixin(object):
     """
     def get_thread(self, request, lock=False, **kwargs):
         thread = self.fetch_thread(request, lock, **kwargs)
+        self.check_forum_permissions(request, thread.forum)
         self.check_thread_permissions(request, thread)
 
         if kwargs.get('thread_slug'):
@@ -58,10 +59,16 @@ class ThreadMixin(object):
         queryset = queryset or Thread.objects
         if lock:
             queryset = queryset.select_for_update()
-        if select_related:
-            queryset = queryset.select_related(*select_related)
 
-        return get_object_or_404(queryset, id=kwargs.get('thread_id'))
+        select_related = select_related or []
+        if not 'forum' in select_related:
+            select_related.append('forum')
+        queryset = queryset.select_related(*select_related)
+
+        where = {'id': kwargs.get('thread_id')}
+        if 'forum_id' in kwargs:
+            where['forum_id'] = kwargs.get('forum_id')
+        return get_object_or_404(queryset, **where)
 
     def check_thread_permissions(self, request, thread):
         add_acl(request.user, thread)
@@ -70,20 +77,36 @@ class ThreadMixin(object):
 
 class PostMixin(object):
     def get_post(self, request, lock=False, **kwargs):
-        thread = self.fetch_post(request, lock, **kwargs)
-        self.check_post_permissions(request, thread)
+        post = self.fetch_post(request, lock, **kwargs)
 
-        return thread
+        post.thread.forum = post.forum
+
+        self.check_forum_permissions(request, post.forum)
+        self.check_thread_permissions(request, post.thread)
+        self.check_post_permissions(request, post)
+
+        return post
 
     def fetch_post(self, request, lock=False, select_related=None,
                    queryset=None, **kwargs):
         queryset = queryset or Post.objects
         if lock:
             queryset = queryset.select_for_update()
-        if select_related:
-            queryset = queryset.select_related(*select_related)
 
-        return get_object_or_404(queryset, id=kwargs.get('post_id'))
+        select_related = select_related or []
+        if not 'forum' in select_related:
+            select_related.append('forum')
+        if not 'thread' in select_related:
+            select_related.append('thread')
+        queryset = queryset.select_related(*select_related)
+
+        where = {'id': kwargs.get('post_id')}
+        if 'thread_id' in kwargs:
+            where['thread_id'] = kwargs.get('thread_id')
+        if 'forum_id' in kwargs:
+            where['forum_id'] = kwargs.get('forum_id')
+
+        return get_object_or_404(queryset, **where)
 
     def check_post_permissions(self, request, post):
         add_acl(request.user, post)

+ 33 - 20
misago/threads/views/generic/posting.py → misago/threads/views/posting.py

@@ -12,14 +12,15 @@ from misago.threads import goto
 from misago.threads.posting import (PostingInterrupt, EditorFormset,
                                     START, REPLY, EDIT)
 from misago.threads.models import Thread, Post, Label
-from misago.threads.permissions import allow_start_thread, allow_reply_thread
+from misago.threads.permissions import (allow_start_thread, allow_reply_thread,
+                                        can_edit_post)
 from misago.threads.views.generic.base import ViewBase
 
 
-__all__ = ['EditorView']
+__all__ = ['PostingView']
 
 
-class EditorView(ViewBase):
+class PostingView(ViewBase):
     """
     Basic view for starting/replying/editing
     """
@@ -33,17 +34,25 @@ class EditorView(ViewBase):
         if is_submit:
             request.user.lock()
 
-        forum = self.get_forum(request, lock=is_submit, **kwargs)
-
+        forum = None
         thread = None
         post = None
 
-        if 'thread_id' in kwargs:
-            thread = self.get_thread(
-                request, lock=is_submit, queryset=forum.thread_set, **kwargs)
+        if 'post_id' in kwargs:
+            post = self.get_post(request, lock=is_submit, **kwargs)
+            forum = post.forum
+            thread = post.thread
+        elif 'thread_id' in kwargs:
+            thread = self.get_thread(request, lock=is_submit, **kwargs)
+            forum = thread.forum
+        else:
+            forum = self.get_forum(request, lock=is_submit, **kwargs)
 
         if thread:
-            mode = REPLY
+            if post:
+                mode = EDIT
+            else:
+                mode = REPLY
         else:
             mode = START
             thread = Thread(forum=forum)
@@ -60,18 +69,18 @@ class EditorView(ViewBase):
         if mode == START:
             self.allow_start(user, forum)
         if mode == REPLY:
-            self.allow_reply(user, forum, thread)
+            self.allow_reply(user, thread)
         if mode == EDIT:
-            self.allow_edit(user, forum, thread, post)
+            self.allow_edit(user, post)
 
     def allow_start(self, user, forum):
         allow_start_thread(user, forum)
 
-    def allow_reply(self, user, forum, thread):
+    def allow_reply(self, user, thread):
         allow_reply_thread(user, thread)
 
-    def allow_edit(self, user, forum, thread, post):
-        raise NotImplementedError()
+    def allow_edit(self, user, post):
+        can_edit_post(user, post)
 
     def dispatch(self, request, *args, **kwargs):
         if request.method == 'POST':
@@ -104,16 +113,20 @@ class EditorView(ViewBase):
                     try:
                         formset.save()
 
-                        if mode == START:
-                            message = _("New thread was posted.")
-                        if mode == REPLY:
-                            message = _("Your reply was posted.")
                         if mode == EDIT:
                             message = _("Message was edited.")
-                        messages.success(request, message)
+                        else:
+                            if mode == START:
+                                message = _("New thread was posted.")
+                            if mode == REPLY:
+                                message = _("Your reply was posted.")
+                            messages.success(request, message)
 
                         return JsonResponse({
-                            'post_url': goto.post(request.user, thread, post)
+                            'message': message,
+                            'post_url': goto.post(request.user, thread, post),
+                            'parsed': post.parsed,
+                            'original': post.original,
                         })
                     except PostingInterrupt as e:
                         return JsonResponse({'interrupt': e.message})

+ 0 - 12
misago/threads/views/threads.py

@@ -31,15 +31,3 @@ class GotoNewView(ThreadsMixin, generic.GotoNewView):
 
 class GotoPostView(ThreadsMixin, generic.GotoPostView):
     pass
-
-
-class StartThreadView(ThreadsMixin, generic.EditorView):
-    pass
-
-
-class ReplyView(ThreadsMixin, generic.EditorView):
-    pass
-
-
-class EditView(ThreadsMixin, generic.EditorView):
-    pass