From aef014f4399d7df80856460d187a1b87a447522f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?R=C3=A9gis=20Hanol?= <regis@hanol.fr>
Date: Fri, 15 Mar 2013 12:56:14 +0100
Subject: [PATCH] displays the number of characters left when editing the topic
 title

---
 .../controllers/composer_controller.js        | 20 +++----
 .../javascripts/discourse/models/composer.js  | 60 ++++++++++---------
 .../templates/composer.js.handlebars          |  4 --
 .../discourse/views/composer_view.js          | 17 +++---
 config/locales/client.en.yml                  |  5 +-
 config/locales/client.fr.yml                  |  5 +-
 6 files changed, 54 insertions(+), 57 deletions(-)

diff --git a/app/assets/javascripts/discourse/controllers/composer_controller.js b/app/assets/javascripts/discourse/controllers/composer_controller.js
index 6ef3ad662..477675d83 100644
--- a/app/assets/javascripts/discourse/controllers/composer_controller.js
+++ b/app/assets/javascripts/discourse/controllers/composer_controller.js
@@ -19,12 +19,13 @@ Discourse.ComposerController = Discourse.Controller.extend({
     return this.get('content').importQuote();
   },
 
+  resetDraftStatus: function() {
+    this.get('content').resetDraftStatus();
+  },
+
   appendText: function(text) {
-    var c;
-    c = this.get('content');
-    if (c) {
-      return c.appendText(text);
-    }
+    var c = this.get('content');
+    if (c) return c.appendText(text);
   },
 
   save: function(force) {
@@ -95,16 +96,11 @@ Discourse.ComposerController = Discourse.Controller.extend({
   },
 
   checkReplyLength: function() {
-    if (this.present('content.reply')) {
-      this.set('hasReply', true);
-    } else {
-      this.set('hasReply', false);
-    }
+    this.set('hasReply', this.present('content.reply'));
   },
 
   saveDraft: function() {
-    var model;
-    model = this.get('content');
+    var model = this.get('content');
     if (model) model.saveDraft();
   },
 
diff --git a/app/assets/javascripts/discourse/models/composer.js b/app/assets/javascripts/discourse/models/composer.js
index e265bd92e..da44a86d4 100644
--- a/app/assets/javascripts/discourse/models/composer.js
+++ b/app/assets/javascripts/discourse/models/composer.js
@@ -28,7 +28,7 @@ Discourse.Composer = Discourse.Model.extend({
     this._super();
     var val = Discourse.KeyValueStore.get('composer.showPreview') || 'true';
     this.set('showPreview', val === 'true');
-    return this.set('archetypeId', Discourse.get('site.default_archetype'));
+    this.set('archetypeId', Discourse.get('site.default_archetype'));
   },
 
   creatingTopic: (function() {
@@ -142,10 +142,7 @@ Discourse.Composer = Discourse.Model.extend({
   }).property('action', 'post', 'topic', 'topic.title'),
 
   toggleText: (function() {
-    if (this.get('showPreview')) {
-      return Em.String.i18n('composer.hide_preview');
-    }
-    return Em.String.i18n('composer.show_preview');
+    return this.get('showPreview') ? Em.String.i18n('composer.hide_preview') : Em.String.i18n('composer.show_preview');
   }).property('showPreview'),
 
   hidePreview: (function() {
@@ -159,14 +156,11 @@ Discourse.Composer = Discourse.Model.extend({
     if (this.get('loading')) return true;
 
     // Title is required on new posts
-    if (this.get('creatingTopic')) {
-      if (this.blank('title')) return true;
-      if (this.get('title').trim().length < Discourse.SiteSettings.min_topic_title_length) return true;
-    }
+    if (this.get('creatingTopic') && this.get('titleLength') < Discourse.SiteSettings.min_topic_title_length) return true;
 
     // Otherwise just reply is required
-    if (this.blank('reply')) return true;
-    if (this.get('reply').trim().length < Discourse.SiteSettings.min_post_length) return true;
+    if (this.get('replyLength') < Discourse.SiteSettings.min_post_length) return true;
+
     return false;
   }).property('reply', 'title', 'creatingTopic', 'loading'),
 
@@ -438,6 +432,7 @@ Discourse.Composer = Discourse.Model.extend({
   saveDraft: function() {
     if (this.get('disableDrafts')) return;
     if (!this.get('reply')) return;
+    if (this.get('titleLength') < Discourse.SiteSettings.min_topic_title_length) return;
     if (this.get('replyLength') < Discourse.SiteSettings.min_post_length) return;
 
     var data = {
@@ -457,28 +452,38 @@ Discourse.Composer = Discourse.Model.extend({
     return Discourse.Draft.save(this.get('draftKey'), this.get('draftSequence'), data).then((function() {
       composer.set('draftStatus', Em.String.i18n('composer.saved_draft_tip'));
     }), (function() {
-      composer.set('draftStatus', 'drafts offline');
+      composer.set('draftStatus', Em.String.i18n('composer.drafts_offline'));
     }));
   },
 
   resetDraftStatus: (function() {
-    var len = Discourse.SiteSettings.min_post_length,
-        replyLength = this.get('replyLength');
-
-    if (replyLength === 0) {
-      this.set('draftStatus', Em.String.i18n('composer.min_length.at_least', { n: len }));
-    } else if (replyLength < len) {
-      this.set('draftStatus', Em.String.i18n('composer.min_length.more', { n: len - replyLength }));
-    } else {
-      this.set('draftStatus', null);
+    // 'title' is focused
+    if ($('#reply-title').is(':focus')) {
+      var titleDiff = Discourse.SiteSettings.min_topic_title_length - this.get('titleLength');
+      if (titleDiff > 0) {
+        return this.set('draftStatus', Em.String.i18n('composer.min_length.need_more_for_title', { n: titleDiff }));
+      }
+    // 'reply' is focused
+    } else if ($('#wmd-input').is(':focus')) {
+      var replyDiff = Discourse.SiteSettings.min_post_length - this.get('replyLength');
+      if (replyDiff > 0) {
+        return this.set('draftStatus', Em.String.i18n('composer.min_length.need_more_for_reply', { n: replyDiff }));
+      }
     }
+    // hide the counters if the currently focused text field is OK
+    this.set('draftStatus', null);
 
   }).observes('reply', 'title'),
 
-  blank: function(prop) {
-    var p = this.get(prop);
-    return !(p && p.length > 0);
-  },
+  /**
+    Computes the length of the title minus non-significant whitespaces
+
+    @property titleLength
+  **/
+  titleLength: function() {
+    var title = this.get('title') || "";
+    return title.replace(/\s+/img, " ").trim().length;
+  }.property('title'),
 
   /**
     Computes the length of the reply minus the quote(s) and non-significant whitespaces
@@ -486,8 +491,7 @@ Discourse.Composer = Discourse.Model.extend({
     @property replyLength
   **/
   replyLength: function() {
-    var reply = this.get('reply');
-    if(!reply) reply = "";
+    var reply = this.get('reply') || "";
     while (Discourse.BBCode.QUOTE_REGEXP.test(reply)) { reply = reply.replace(Discourse.BBCode.QUOTE_REGEXP, ""); }
     return reply.replace(/\s+/img, " ").trim().length;
   }.property('reply')
@@ -547,5 +551,3 @@ Discourse.Composer.reopenClass({
   // Draft key
   REPLY_AS_NEW_TOPIC_KEY: REPLY_AS_NEW_TOPIC_KEY
 });
-
-
diff --git a/app/assets/javascripts/discourse/templates/composer.js.handlebars b/app/assets/javascripts/discourse/templates/composer.js.handlebars
index 55180cf3d..547edf0a6 100644
--- a/app/assets/javascripts/discourse/templates/composer.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/composer.js.handlebars
@@ -17,7 +17,6 @@
 
           {{#if content.editTitle}}
             <div class='form-element clearfix'>
-
                 {{#if content.creatingPrivateMessage}}
                   {{view Discourse.TextField id="private-message-users" class="span8" placeholderKey="composer.users_placeholder"}}
                 {{/if}}
@@ -31,7 +30,6 @@
             </div>
           {{/if}}
 
-
           <div class='wmd-controls'>
             <div class='textarea-wrapper'>
               <div class='wmd-button-bar' id='wmd-button-bar'></div>
@@ -43,7 +41,6 @@
             {{#if Discourse.currentUser}}
                <a href="#" {{action togglePreview target="controller"}} class='toggle-preview'>{{{content.toggleText}}}</a>
                <div class='saving-draft'></div>
-
             {{/if}}
           </div>
 
@@ -51,7 +48,6 @@
             <div class='submit-panel'>
               <button {{action save target="controller"}} tabindex="3" {{bindAttr disabled="content.cantSubmitPost"}} class='btn btn-primary create'>{{view.content.saveText}}</button>
               <a href='#' {{action cancel target="controller"}} class='cancel' tabindex="4">{{i18n cancel}}</a>
-
               {{#if view.loadingImage}}
                 <div id="image-uploading">
                   {{i18n image_selector.uploading_image}} {{view.uploadProgress}}% <a {{action cancelUpload target="view"}}>{{i18n cancel}}</a>
diff --git a/app/assets/javascripts/discourse/views/composer_view.js b/app/assets/javascripts/discourse/views/composer_view.js
index 8860b0911..6c503199b 100644
--- a/app/assets/javascripts/discourse/views/composer_view.js
+++ b/app/assets/javascripts/discourse/views/composer_view.js
@@ -22,8 +22,7 @@ Discourse.ComposerView = Discourse.View.extend({
   educationClosed: null,
 
   composeState: (function() {
-    var state;
-    state = this.get('content.composeState');
+    var state = this.get('content.composeState');
     if (!state) {
       state = Discourse.Composer.CLOSED;
     }
@@ -103,8 +102,7 @@ Discourse.ComposerView = Discourse.View.extend({
   }).property('content.composeState', 'content.reply', 'educationClosed', 'educationContents'),
 
   newUserEducationVisibilityChanged: (function() {
-    var $panel;
-    $panel = $('#new-user-education');
+    var $panel = $('#new-user-education');
     if (this.get('newUserEducationVisible')) {
       return $panel.slideDown('fast');
     } else {
@@ -116,6 +114,11 @@ Discourse.ComposerView = Discourse.View.extend({
     $('#new-user-education').css('bottom', sizePx);
   },
 
+  focusIn: (function() {
+    var controller = this.get('controller');
+    if(controller) controller.resetDraftStatus();
+  }),
+
   resize: (function() {
     // this still needs to wait on animations, need a clean way to do that
     var _this = this;
@@ -130,15 +133,13 @@ Discourse.ComposerView = Discourse.View.extend({
   }).observes('content.composeState'),
 
   keyUp: function(e) {
-    var controller;
-    controller = this.get('controller');
+    var controller = this.get('controller');
     controller.checkReplyLength();
     if (e.which === 27) controller.hitEsc();
   },
 
   didInsertElement: function() {
-    var replyControl;
-    replyControl = $('#reply-control');
+    var replyControl = $('#reply-control');
     replyControl.DivResizer({
       resize: this.resize,
       onDrag: this.moveNewUserEducation
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index d599f2f29..e1ae3ff49 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -267,10 +267,11 @@ en:
       saving_draft_tip: "saving"
       saved_draft_tip: "saved"
       saved_local_draft_tip: "saved locally"
+      drafts_offline: "drafts offline"
 
       min_length:
-        at_least: "enter at least {{n}} characters"
-        more: "{{n}} to go..."
+        need_more_for_title: "{{n}} to go for the title"
+        need_more_for_reply: "{{n}} to go for the reply"
 
       save_edit: "Save Edit"
       reply_original: "Reply on Original Topic"
diff --git a/config/locales/client.fr.yml b/config/locales/client.fr.yml
index 8b8a29874..1fa144a2e 100644
--- a/config/locales/client.fr.yml
+++ b/config/locales/client.fr.yml
@@ -271,10 +271,11 @@ fr:
       saving_draft_tip: "sauvegarde..."
       saved_draft_tip: "sauvegardé"
       saved_local_draft_tip: "sauvegardé en local"
+      drafts_offline: "sauvegardé hors ligne"
 
       min_length:
-        at_least: "Saisir au moins {{n}} caractères"
-        more: "{{n}} restants..."
+        need_more_for_title: "{{n}} caractères restant pour le titre"
+        need_more_for_reply: "{{n}} caractères restant pour le message"
 
       save_edit: "Sauvegarder la modification"
       reply_original: Répondre à la discussion initiale