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

+ 1 - 0
misago/conf/defaults.py

@@ -72,6 +72,7 @@ PIPELINE_JS = {
             'misago/js/misago-scrolling.js',
             'misago/js/misago-notifications.js',
             'misago/js/misago-threads-lists.js',
+            'misago/js/misago-posting.js',
         ),
         'output_filename': 'misago.js',
     },

+ 6 - 2
misago/static/misago/css/misago/editor.less

@@ -7,10 +7,14 @@
 //
 //**
 .misago-editor {
+  border: 2px solid @form-panel-border;
+  border-radius: @border-radius-large;
+  overflow: hidden;
+  margin-bottom: @line-height-computed;
+
   .editor-toolbar {
     background: @form-panel-footer-bg;
-    border: 1px solid @form-panel-border;
-    border-width: 1px 0px;
+    border-bottom: 1px solid @form-panel-border;
     padding: @padding-small-vertical @padding-small-horizontal;
 
     &>ul {

+ 5 - 0
misago/static/misago/css/misago/forms.less

@@ -134,6 +134,11 @@
         }
       }
 
+      .misago-editor {
+        margin: @form-panel-padding;
+        margin-top: 0px;
+      }
+
       .help-block {
         color: @field-help-block-color;
         margin-bottom: 0px;

+ 1 - 0
misago/static/misago/css/misago/misago.less

@@ -24,6 +24,7 @@
 @import "footer.less";
 
 @import "forms.less";
+@import "posting.less";
 @import "forums.less";
 @import "notifications.less";
 @import "userslists.less";

+ 71 - 0
misago/static/misago/css/misago/posting.less

@@ -0,0 +1,71 @@
+//
+// Post editor
+// --------------------------------------------------
+
+
+.editor-preview {
+  padding-top: @line-height-computed;
+  padding-bottom: @line-height-computed * 2;
+
+  .misago-markup {
+    // finetuned height to avoid page height form changing for most common posts
+    margin-top: 6px;
+    min-height: 33px;
+  }
+}
+
+
+.posting-container {
+  background: @form-panel-bg;
+  border: 1px solid @form-panel-border;
+  border-width: 1px 0px;
+  box-shadow: 0px 0px 0px 3px @form-panel-shadow;
+  padding: @form-panel-padding;
+  padding-bottom: 0px;
+  padding-left: 0px;
+  padding-right: 0px;
+
+  &.fixed {
+    border-bottom: none;
+    position: fixed;
+    bottom: 0px;
+    width: 100%;
+    z-index: 2;
+  }
+
+  form>.container-fluid {
+    padding-left: @grid-gutter-width;
+    padding-right: @grid-gutter-width;
+  }
+
+  .form-main {
+    &>* {
+      margin: @form-panel-padding;
+      margin-top: 0px;
+      margin-left: 0px;
+      margin-right: 0px;
+    }
+  }
+
+  .form-side {
+    h3 {
+      padding: 0px;
+      margin: 0px;
+      margin-bottom: @line-height-computed / 2;
+
+      color: @text-muted;
+      font-size: @font-size-base;
+      font-weight: bold;
+    }
+
+    .supporting-form {
+      margin-bottom: @line-height-computed / 2;
+    }
+  }
+
+  .form-footer {
+    background-color: @form-panel-footer-bg;
+    border-top: 1px solid @form-panel-border;
+    padding: @form-panel-padding;
+  }
+}

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

@@ -19,6 +19,7 @@
 
       .panel-default {
         position: relative;
+
         &:after, &:before {
           right: 100%;
           border: solid transparent;

+ 138 - 0
misago/static/misago/js/misago-posting.js

@@ -0,0 +1,138 @@
+// Controller for posting actions
+$(function() {
+  MisagoPreview = function(_controller, options) {
+
+    this.$form = options.form;
+    this.$area = $(options.selector);
+
+    this.$markup = this.$area.find('.misago-markup');
+    this.$message = this.$area.find('.empty-message');
+
+    if (this.$markup.text() == '') {
+      this.$markup.hide();
+    }
+
+    this.api_url = options.api_url;
+    this.active = true;
+    this.previewed_data = this.$form.serialize() + '&preview=1';
+
+
+    this.frequency = 1500;
+
+    this.height = this.$markup.height();
+
+    var _this = this;
+
+    this.last_key_press = (new Date().getTime() / 1000);
+    this.$form.find('.misago-editor textarea').keyup(function() {
+      _this.last_key_press = (new Date().getTime() / 1000);
+    })
+
+    this.update = function() {
+      var form_data = _this.$form.serialize() + '&preview=1';
+      var last_key = (new Date().getTime() / 1000) - _this.last_key_press;
+
+      if (_this.previewed_data != form_data && last_key > 2) {
+        $.post(_this.api_url, form_data, function(data) {
+          var scroll = $(document).height() - $(document).scrollTop();
+
+          if (data.preview) {
+            if (_this.$message.is(":visible")) {
+              _this.$message.fadeOut(function() {
+                _this.$markup.html(data.preview);
+                _this.$markup.fadeIn();
+              });
+            } else {
+              _this.$markup.html(data.preview);
+            }
+
+            if (_this.$markup.height() > _this.height) {
+              $(document).scrollTop($(document).height() - scroll);
+            }
+          } else {
+            _this.$markup.fadeOut(function() {
+              _this.$markup.html("");
+              _this.$message.fadeIn();
+            });
+          }
+
+          _controller.update_affix_end();
+          _controller.update_affix();
+          Misago.DOM.changed();
+
+          _this.previewed_data = form_data;
+
+          // set timeout
+          if (_this.active) {
+            window.setTimeout(function() {
+              _this.update();
+            }, _this.frequency);
+          }
+
+        });
+      } else if (_this.active) {
+        window.setTimeout(function() {
+          _this.update();
+        }, _this.frequency);
+      }
+    }
+
+    this.stop = function() {
+      this.active = false;
+    }
+
+  }
+
+  MisagoPosting = function() {
+
+    this.$spacer = null;
+    this.$container = null;
+    this.$form = null;
+
+    this.$preview = null;
+
+    this.affix_end = 0;
+
+    var _this = this;
+
+    this.init = function(options) {
+
+      this.$form = $('#posting-form');
+      this.$container = this.$form.parent();
+      this.$spacer = this.$container.parent();
+
+      if (options.preview !== undefined) {
+        this.$preview = new MisagoPreview(this, {selector: options.preview, form: this.$form, api_url: options.api_url});
+        this.$preview.update();
+      }
+
+      this.container_height = this.$container.innerHeight();
+      this.$spacer.height(this.container_height);
+
+      this.heights_diff = this.$container.outerHeight() - this.$spacer.innerHeight();
+
+      this.update_affix_end();
+      this.update_affix();
+
+      $(document).scroll(function() {
+        _this.update_affix()
+      });
+
+    }
+
+    this.update_affix_end = function() {
+      this.spacer_end = this.$spacer.offset().top + this.$spacer.height();
+    }
+
+    this.update_affix = function() {
+      if (this.spacer_end - $(document).scrollTop() > $(window).height()) {
+        this.$container.addClass('fixed');
+      } else {
+        this.$container.removeClass('fixed');
+      }
+    }
+
+  }
+
+  Misago.Posting = new MisagoPosting();
+});

+ 10 - 4
misago/static/misago/js/misago-yesnoswitch.js

@@ -5,7 +5,7 @@ function enableYesNoSwitch(selector) {
     var value = $control.find("input:checked").val() * 1;
 
     // hide original switch options
-    $control.find('label').addClass('hidden-original-switch');
+    $control.find('ul, label').addClass('hidden-original-switch');
 
     var yes_label = $.trim($control.find('label').first().text());
     var no_label = $.trim($control.find('label').last().text());
@@ -52,9 +52,12 @@ function enableYesNoSwitch(selector) {
   }
 
   $(selector).each(function() {
-    if ($(this).find('.yesno-switch').length == 2) {
-      createYesNoSwitch($(this));
-    }
+    if ($(this).data('misago-yes-no-switch') == undefined) {
+      $(this).data('misago-yes-no-switch', 'ok');
+      if ($(this).find('.yesno-switch').length == 2) {
+        createYesNoSwitch($(this));
+      }
+    };
   });
 }
 
@@ -62,4 +65,7 @@ function enableYesNoSwitch(selector) {
 // Enable switch
 $(function() {
   enableYesNoSwitch('.control-radioselect');
+  Misago.DOM.on_change(function() {
+    enableYesNoSwitch('.control-radioselect');
+  });
 });

+ 1 - 1
misago/templates/misago/editor/body.html

@@ -59,7 +59,7 @@
   </div>
 
   <div class="editor-textarea">
-    <textarea id="{{ editor.field.auto_id }}" name="{{ editor.field.html_name }}" rows="5">{% if editor.field.value %}{{ editor.field.value }}{% endif %}</textarea>
+    <textarea id="{{ editor.field.auto_id }}" name="{{ editor.field.html_name }}" rows="6">{% if editor.field.value %}{{ editor.field.value }}{% endif %}</textarea>
   </div>
 
   <div class="editor-footer">

+ 59 - 0
misago/templates/misago/posting/editorview.html

@@ -0,0 +1,59 @@
+{% extends "misago/base.html" %}
+{% load i18n %}
+
+
+{% block title %}{% trans "Start new thread" %} | {{ forum }} | {{ 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 %}
+        <li>
+          <a href="{{ crumb.get_absolute_url }}">{{ crumb }}</a>{% if not forloop.last %}<span class="fa fa-chevron-right"></span>{% endif %}
+        </li>
+        {% endfor %}
+      </ol>
+      {% endif %}
+
+      <h1>
+        <span class="main">
+          <a href="{{ forum.get_absolute_url }}">{{ forum }}</a>
+        </span>
+        <span class="sub">
+          <span class="fa fa-chevron-right"></span>
+          {% trans "Start new thread" %}
+        </span>
+      </h1>
+    </div>
+  </div>
+  <div class="container">
+    <div id="preview-area" class="editor-preview">
+      <p class="lead empty-message">
+        {% trans "Once you start writing mesage, it's preview will be displayed here." %}
+      </p>
+      <article class="misago-markup"></article>
+      <p class="text-muted small">
+        <span class="fa fa-refresh fa-fw"></span>
+        {% trans "Preview updates automatically when you pause typing." %}
+      </p>
+    </div>
+  </div>
+
+  {% include "misago/posting/form.html" %}
+
+</div>
+{% endblock content %}
+
+
+{% block javascripts %}
+{{ block.super }}
+<script lang="JavaScript">
+  $(function() {
+    var reply_editor = Misago.Posting.init({preview: '#preview-area', api: '{{ api_url }}'});
+  });
+</script>
+{% endblock javascripts %}

+ 35 - 73
misago/templates/misago/posting/form.html

@@ -1,94 +1,56 @@
-{% extends "misago/base.html" %}
 {% load i18n %}
+<div class="posting-spacer">
+  <div class="posting-container">
+    <form id="posting-form" method="POST">
 
+      <div class="container-fluid">
+        {% csrf_token %}
 
-{% block title %}{% trans "Start new thread" %} | {{ forum }} | {{ block.super }}{% endblock title %}
+        <div class="row">
+          {% if supporting_forms %}
+          <div class="col-md-9 form-main">
+          {% else %}
+          <div class="col-md-12 form-main">
+          {% endif %}
 
-
-{% 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 %}
-        <li>
-          <a href="{{ crumb.get_absolute_url }}">{{ crumb }}</a>{% if not forloop.last %}<span class="fa fa-chevron-right"></span>{% endif %}
-        </li>
-        {% endfor %}
-      </ol>
-      {% endif %}
-
-      <h1>
-        <span class="main">
-          <a href="{{ forum.get_absolute_url }}">{{ forum }}</a>
-        </span>
-        <span class="sub">
-          <span class="fa fa-chevron-right"></span>
-          {% trans "Start new thread" %}
-        </span>
-      </h1>
-    </div>
-  </div>
-  <div class="container">
-    <form method="POST">
-      {% csrf_token %}
-
-      <div class="row">
-        {% if supporting_forms %}
-        <div class="col-md-9">
-        {% else %}
-        <div class="col-md-10 col-md-offset-1">
-        {% endif %}
-
-          <div class="form-panel">
             {% for form in main_forms %}
             {% include form.template %}
             {% endfor %}
 
-            <div class="form-footer text-right">
+          </div>
+
+          {% if supporting_forms %}
+          <div class="col-md-3 form-side">
 
-              {% if formset.start_form %}
-              <button class="btn btn-primary" name="submit">{% trans "Post thread" %}</button>
-              {% elif formset.reply_form %}
-              <button class="btn btn-primary" name="submit">{% trans "Post reply" %}</button>
-              {% elif formset.edit_form %}
-              <button class="btn btn-primary" name="submit">{% trans "Save changes" %}</button>
-              {% endif %}
+            <h3>{% trans "Thread options:" %}</h3>
 
+            {% for form in supporting_forms %}
+            <div class="supporting-form">
+              {% include form.template %}
             </div>
+            {% endfor %}
+
           </div>
+          {% endif %}
+        </div><!-- /.row -->
 
-        </div>
 
-        {% if supporting_forms %}
-        <div class="col-md-3">
+      </div>
 
-          <div class="form-panel form-side-panel">
-            <div class="form-body">
-              {% for form in supporting_forms %}
-              <fieldset>
-                <legend>{{ form.legend }}</legend>
-                {% include form.template %}
-              </fieldset>
-              {% endfor %}
-            </div>
-          </div>
+      <div class="form-footer">
+        <div class="container-fluid">
+
+          {% if formset.start_form %}
+          <button class="btn btn-primary" name="submit">{% trans "Post thread" %}</button>
+          {% elif formset.reply_form %}
+          <button class="btn btn-primary" name="submit">{% trans "Post reply" %}</button>
+          {% elif formset.edit_form %}
+          <button class="btn btn-primary" name="submit">{% trans "Save changes" %}</button>
+          {% endif %}
 
         </div>
-        {% endif %}
-      </div><!-- /.row -->
+      </div>
 
     </form>
   </div>
 </div>
-{% endblock content %}
-
-
-{% block javascripts %}
-{% for form in main_forms %}
-  {% if form.js_template %}
-  {% include form.js_template %}
-  {% endif %}
-{% endfor %}
-{% endblock javascripts %}

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

@@ -2,11 +2,7 @@
 {% include 'misago/form_errors.html' %}
 
 {% if form.title %}
-<div class="form-header">
-  <input class="textinput textInput form-control input-lg" id="{{ form.title.auto_id }}" name="title" type="text" {% if form.title.value %}value="{{ form.title.value }}"{% endif %} placeholder="{% trans "Thread title..." %}"/>
-</div>
+<input class="textinput textInput form-control input-lg" id="{{ form.title.auto_id }}" name="title" type="text" {% if form.title.value %}value="{{ form.title.value }}"{% endif %} placeholder="{% trans "Thread title..." %}"/>
 {% endif %}
 
-<div class="form-body">
-  {% editor_body form.post_editor %}
-</div>
+{% editor_body form.post_editor %}

+ 3 - 1
misago/templates/misago/posting/threadcloseform.html

@@ -1,2 +1,4 @@
 {% load misago_forms %}
-{% form_input form.is_closed %}
+<div class="controls control-radioselect">
+  {% form_input form.is_closed %}
+</div>

+ 3 - 1
misago/templates/misago/posting/threadpinform.html

@@ -1,2 +1,4 @@
 {% load misago_forms %}
-{% form_input form.is_pinned %}
+<div class="controls control-radioselect">
+  {% form_input form.is_pinned %}
+</div>

+ 1 - 1
misago/templates/misago/usercp/edit_signature.html

@@ -42,7 +42,7 @@
       </div>
     </div>
     {% else %}
-    <div class="form-body">
+    <div class="form-body no-fieldsets">
 
       {% editor_body editor %}
 

+ 9 - 3
misago/threads/forms/posting.py

@@ -111,10 +111,13 @@ def ThreadLabelForm(*args, **kwargs):
 
 class ThreadPinForm(forms.Form):
     is_supporting = True
-    legend = _("Thread weight")
+    legend = _("Pin thread")
     template = "misago/posting/threadpinform.html"
 
-    is_pinned = forms.YesNoSwitch(label=_("Pin thread"), initial=0)
+    is_pinned = forms.YesNoSwitch(
+        label=_("Pin thread"),
+        yes_label=_("Pin thread"),
+        no_label=_("Unpin thread"))
 
 
 class ThreadCloseForm(forms.Form):
@@ -122,4 +125,7 @@ class ThreadCloseForm(forms.Form):
     legend = _("Close thread")
     template = "misago/posting/threadcloseform.html"
 
-    is_closed = forms.YesNoSwitch(label=_("Close thread"), initial=0)
+    is_closed = forms.YesNoSwitch(
+        label=_("Close thread"),
+        yes_label=_("Close thread"),
+        no_label=_("Open thread"))

+ 2 - 1
misago/threads/posting/reply.py

@@ -24,7 +24,8 @@ class ReplyFormMiddleware(PostingMiddleware):
         return form
 
     def pre_save(self, form):
-        self.parsing_result.update(form.parsing_result)
+        if form.is_valid():
+            self.parsing_result.update(form.parsing_result)
 
     def save(self, form):
         if self.mode == START:

+ 4 - 3
misago/threads/posting/threadclose.py

@@ -18,6 +18,7 @@ class ThreadCloseFormMiddleware(PostingMiddleware):
             return ThreadCloseForm(prefix=self.prefix, initial=initial)
 
     def pre_save(self, form):
-        if self.thread_is_closed != form.cleaned_data.get('is_closed'):
-            self.thread.is_closed = form.cleaned_data.get('is_closed')
-            self.thread.update_fields.append('is_closed')
+        if form.is_valid():
+            if self.thread_is_closed != form.cleaned_data.get('is_closed'):
+                self.thread.is_closed = form.cleaned_data.get('is_closed')
+                self.thread.update_fields.append('is_closed')

+ 8 - 7
misago/threads/posting/threadlabel.py

@@ -21,10 +21,11 @@ class ThreadLabelFormMiddleware(PostingMiddleware):
                                     initial=initial)
 
     def pre_save(self, form):
-        if self.thread_label_id != form.cleaned_data.get('label'):
-            if form.cleaned_data.get('label'):
-                self.thread.label_id = form.cleaned_data.get('label')
-                self.thread.update_fields.append('label')
-            else:
-                self.thread.label = None
-                self.thread.update_fields.append('label')
+        if form.is_valid():
+            if self.thread_label_id != form.cleaned_data.get('label'):
+                if form.cleaned_data.get('label'):
+                    self.thread.label_id = form.cleaned_data.get('label')
+                    self.thread.update_fields.append('label')
+                else:
+                    self.thread.label = None
+                    self.thread.update_fields.append('label')

+ 5 - 4
misago/threads/posting/threadpin.py

@@ -5,7 +5,7 @@ from misago.threads.posting import PostingMiddleware
 class ThreadPinFormMiddleware(PostingMiddleware):
     def use_this_middleware(self):
         if self.forum.acl['can_pin_threads']:
-            self.thread_is_pinned = self.thread.is_pinned
+            self.is_pinned = self.thread.is_pinned
             return True
         else:
             return False
@@ -18,6 +18,7 @@ class ThreadPinFormMiddleware(PostingMiddleware):
             return ThreadPinForm(prefix=self.prefix, initial=initial)
 
     def pre_save(self, form):
-        if self.thread_is_pinned != form.cleaned_data.get('is_pinned'):
-            self.thread.is_pinned = form.cleaned_data.get('is_pinned')
-            self.thread.update_fields.append('is_pinned')
+        if form.is_valid():
+            if self.is_pinned != form.cleaned_data.get('is_pinned'):
+                self.thread.is_pinned = form.cleaned_data.get('is_pinned')
+                self.thread.update_fields.append('is_pinned')

+ 14 - 3
misago/threads/views/generic/posting.py

@@ -1,6 +1,7 @@
 from django.contrib import messages
 from django.db.models import Q
 from django.db.transaction import atomic
+from django.http import JsonResponse
 from django.shortcuts import redirect, render
 from django.utils.translation import ugettext_lazy, ugettext as _
 from django.views.generic import View
@@ -21,7 +22,7 @@ class EditorView(ViewBase):
     """
     Basic view for starting/replying/editing
     """
-    template = 'misago/posting/form.html'
+    template = 'misago/posting/editorview.html'
 
     def find_mode(self, request, *args, **kwargs):
         """
@@ -91,8 +92,17 @@ class EditorView(ViewBase):
                     return redirect(thread.get_absolute_url())
                 except PostingInterrupt as e:
                     messages.error(request, e.message)
-            else:
-                formset.update()
+
+            if request.is_ajax():
+                if 'form' in request.POST:
+                    pass
+
+                if 'preview' in request.POST:
+                    formset.update()
+                    return JsonResponse({
+                        'preview': formset.post.parsed
+                    })
+
 
         return self.render(request, {
             'mode': mode,
@@ -105,4 +115,5 @@ class EditorView(ViewBase):
             'thread': thread,
             'post': post,
             'quote': quote,
+            'api_url': request.path
         })