From 532b1f5450fec8c93b2d2124e6e4bbff48fb417f Mon Sep 17 00:00:00 2001
From: Robin Ward <robin.ward@gmail.com>
Date: Thu, 21 Feb 2013 18:09:56 -0500
Subject: [PATCH] Can edit category descriptions, they show up in a `title`
 attribute

---
 .../discourse/components/utilities.js         | 22 ++++--
 .../javascripts/discourse/models/composer.js  |  1 +
 .../javascripts/discourse/models/post.js      | 15 ++--
 .../javascripts/discourse/models/site.js      |  8 ++
 .../modal/edit_category.js.handlebars         | 13 +++-
 .../views/modal/edit_category_view.js         | 60 ++++++++-------
 app/controllers/posts_controller.rb           | 33 +++++---
 app/models/category.rb                        | 21 ++----
 app/models/site_setting.rb                    |  2 -
 app/serializers/category_serializer.rb        | 10 ++-
 config/locales/client.en.yml                  |  3 +
 config/locales/nl.yml.working                 |  1 -
 config/locales/server.en.yml                  |  3 +-
 config/locales/server.fr.yml                  |  1 -
 config/locales/server.nl.yml                  |  1 -
 ...221215017_add_description_to_categories.rb | 25 +++++++
 db/structure.sql                              | 39 +++++-----
 lib/post_revisor.rb                           | 23 ++++++
 spec/components/post_revisor_spec.rb          | 75 +++++++++++++++++++
 spec/controllers/posts_controller_spec.rb     |  2 +-
 spec/models/category_spec.rb                  | 13 ++--
 21 files changed, 268 insertions(+), 103 deletions(-)
 create mode 100644 db/migrate/20130221215017_add_description_to_categories.rb

diff --git a/app/assets/javascripts/discourse/components/utilities.js b/app/assets/javascripts/discourse/components/utilities.js
index 4b33c9ad843..fc6b1040d87 100644
--- a/app/assets/javascripts/discourse/components/utilities.js
+++ b/app/assets/javascripts/discourse/components/utilities.js
@@ -40,16 +40,22 @@
     */
 
     categoryLink: function(category) {
-      var color, name;
-      if (!category) {
-        return "";
-      }
+      var color, name, description, result;
+      if (!category) return "";
+
       color = Em.get(category, 'color');
       name = Em.get(category, 'name');
-      return "<a href=\"/category/" + 
-             (this.categoryUrlId(category)) + 
-             "\" class=\"badge-category excerptable\" data-excerpt-size=\"medium\" style=\"background-color: #" + color + "\">" + 
-             name + "</a>";
+      description = Em.get(category, 'description');
+
+      // Build the HTML link
+      result = "<a href=\"/category/" + 
+                this.categoryUrlId(category) + 
+                "\" class=\"badge-category excerptable\" data-excerpt-size=\"medium\" ";
+
+      // Add description if we have it
+      if (description) result += "title=\"" + description + "\" ";
+
+      return result + "style=\"background-color: #" + color + "\">" + name + "</a>";
     },
     avatarUrl: function(username, size, template) {
       var rawSize;
diff --git a/app/assets/javascripts/discourse/models/composer.js b/app/assets/javascripts/discourse/models/composer.js
index 176d5ab2ed2..0fd05e781c1 100644
--- a/app/assets/javascripts/discourse/models/composer.js
+++ b/app/assets/javascripts/discourse/models/composer.js
@@ -312,6 +312,7 @@
       post.set('cooked', jQuery('#wmd-preview').html());
       this.set('composeState', CLOSED);
       post.save(function(savedPost) {
+       
         var idx, postNumber, posts;
         posts = _this.get('topic.posts');
         /* perhaps our post came from elsewhere eg. draft
diff --git a/app/assets/javascripts/discourse/models/post.js b/app/assets/javascripts/discourse/models/post.js
index 8c4a581766c..c05a93606cf 100644
--- a/app/assets/javascripts/discourse/models/post.js
+++ b/app/assets/javascripts/discourse/models/post.js
@@ -144,22 +144,25 @@
           url: "/posts/" + (this.get('id')),
           type: 'PUT',
           data: {
-            post: {
-              raw: this.get('raw')
-            },
+            post: { raw: this.get('raw') },
             image_sizes: this.get('imageSizes')
           },
           success: function(result) {
-            return typeof complete === "function" ? complete(Discourse.Post.create(result)) : void 0;
+
+            console.log(result)
+            
+            // If we received a category update, update it
+            if (result.category) Discourse.get('site').updateCategory(result.category);
+
+            return typeof complete === "function" ? complete(Discourse.Post.create(result.post)) : void 0;
           },
           error: function(result) {
             return typeof error === "function" ? error(result) : void 0;
           }
         });
       } else {
-        /* We're saving a post
-        */
 
+        // We're saving a post
         data = {
           post: this.getProperties('raw', 'topic_id', 'reply_to_post_number', 'category'),
           archetype: this.get('archetype'),
diff --git a/app/assets/javascripts/discourse/models/site.js b/app/assets/javascripts/discourse/models/site.js
index ec5f1cf7d95..8a349c20abe 100644
--- a/app/assets/javascripts/discourse/models/site.js
+++ b/app/assets/javascripts/discourse/models/site.js
@@ -1,6 +1,7 @@
 (function() {
 
   window.Discourse.Site = Discourse.Model.extend({
+
     notificationLookup: (function() {
       var result;
       result = [];
@@ -9,6 +10,7 @@
       });
       return result;
     }).property('notification_types'),
+
     flagTypes: (function() {
       var postActionTypes;
       postActionTypes = this.get('post_action_types');
@@ -17,8 +19,14 @@
       }
       return postActionTypes.filterProperty('is_flag', true);
     }).property('post_action_types.@each'),
+
     postActionTypeById: function(id) {
       return this.get("postActionByIdLookup.action" + id);
+    },
+
+    updateCategory: function(newCategory) {
+      var existingCategory = this.get('categories').findProperty('id', Em.get(newCategory, 'id'));
+      if (existingCategory) existingCategory.mergeAttributes(newCategory);
     }
   });
 
diff --git a/app/assets/javascripts/discourse/templates/modal/edit_category.js.handlebars b/app/assets/javascripts/discourse/templates/modal/edit_category.js.handlebars
index 9415888ce0c..0db67a2d301 100644
--- a/app/assets/javascripts/discourse/templates/modal/edit_category.js.handlebars
+++ b/app/assets/javascripts/discourse/templates/modal/edit_category.js.handlebars
@@ -3,10 +3,17 @@
     <label>{{i18n category.name}}</label>
     {{view Discourse.TextField valueBinding="view.category.name" placeholderKey="category.name_placeholder"}}
 
-    {{#if view.category.excerpt}}
+    <div class='description-controls' style="margin-bottom: 20px">
       <label>{{i18n category.description}}</label>
-      <p>{{view.category.excerpt}} <a href="{{unbound view.category.topic_url}}">{{i18n category.topic}}</a></p>
-    {{/if}}
+
+      {{#if view.category.description}}
+        {{view.category.description}}
+        <a href="#" {{action showCategoryTopic target="view"}}>{{i18n category.change_in_category_topic}}</a>
+      {{else}}
+        {{i18n category.no_description}} <a {{action showCategoryTopic target="view"}} href="#">{{i18n category.change_in_category_topic}}</a>
+      {{/if}}
+
+    </div>
 
     <label>{{i18n category.color}}</label>
 
diff --git a/app/assets/javascripts/discourse/views/modal/edit_category_view.js b/app/assets/javascripts/discourse/views/modal/edit_category_view.js
index 3d7e8814788..988e6e2b450 100644
--- a/app/assets/javascripts/discourse/views/modal/edit_category_view.js
+++ b/app/assets/javascripts/discourse/views/modal/edit_category_view.js
@@ -1,64 +1,72 @@
 (function() {
 
-  window.Discourse.EditCategoryView = window.Discourse.ModalBodyView.extend({
+  /**
+    A modal view for editing the basic aspects of a category
+
+    @class EditCategoryView    
+    @extends Discourse.ModalBodyView
+    @namespace Discourse
+    @module Discourse
+  **/ 
+  Discourse.EditCategoryView = Discourse.ModalBodyView.extend({
     templateName: 'modal/edit_category',
     appControllerBinding: 'Discourse.appController',
+
     disabled: (function() {
-      if (this.get('saving')) {
-        return true;
-      }
-      if (!this.get('category.name')) {
-        return true;
-      }
-      if (!this.get('category.color')) {
-        return true;
-      }
+      if (this.get('saving')) return true;
+      if (!this.get('category.name')) return true;
+      if (!this.get('category.color')) return true;
       return false;
     }).property('category.name', 'category.color'),
+
     colorStyle: (function() {
       return "background-color: #" + (this.get('category.color')) + ";";
     }).property('category.color'),
+
     title: (function() {
-      if (this.get('category.id')) {
-        return "Edit Category";
-      } else {
-        return "Create Category";
-      }
+      if (this.get('category.id')) return Em.String.i18n("category.edit_long");
+      return "Create Category";
     }).property('category.id'),
+
     buttonTitle: (function() {
-      if (this.get('saving')) {
-        return "Saving...";
-      } else {
-        return this.get('title');
-      }
+      if (this.get('saving')) return Em.String.i18n("saving");
+      return this.get('title');
     }).property('title', 'saving'),
+
     didInsertElement: function() {
       this._super();
       if (this.get('category')) {
-        return this.set('id', this.get('category.slug'));
+        this.set('id', this.get('category.slug'));
       } else {
-        return this.set('category', Discourse.Category.create({
-          color: 'AB9364'
-        }));
+        this.set('category', Discourse.Category.create({ color: 'AB9364' }));
       }
     },
+
+    showCategoryTopic: function() {
+      jQuery('#discourse-modal').modal('hide');
+      Discourse.routeTo(this.get('category.topic_url'));
+      return false;
+    },
+
     saveSuccess: function(result) {
       jQuery('#discourse-modal').modal('hide');
       window.location = "/category/" + (Discourse.Utilities.categoryUrlId(result.category));
     },
+
     saveCategory: function() {
       var _this = this;
       this.set('saving', true);
       return this.get('category').save({
         success: function(result) {
-          return _this.saveSuccess(result);
+          _this.saveSuccess(result);
         },
         error: function(errors) {
           _this.displayErrors(errors);
-          return _this.set('saving', false);
+          _this.set('saving', false);
         }
       });
     }
+
   });
 
 }).call(this);
diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb
index 9607c2ece3c..819aca614d9 100644
--- a/app/controllers/posts_controller.rb
+++ b/app/controllers/posts_controller.rb
@@ -35,23 +35,32 @@ class PostsController < ApplicationController
   def update
     requires_parameter(:post)
 
-    @post = Post.where(id: params[:id]).first
-    @post.image_sizes = params[:image_sizes] if params[:image_sizes].present?
-    guardian.ensure_can_edit!(@post)
-    if @post.revise(current_user, params[:post][:raw])
-      TopicLink.extract_from(@post)
+    post = Post.where(id: params[:id]).first
+    post.image_sizes = params[:image_sizes] if params[:image_sizes].present?
+    guardian.ensure_can_edit!(post)
+
+    revisor = PostRevisor.new(post)
+    if revisor.revise!(current_user, params[:post][:raw])
+      TopicLink.extract_from(post)
     end
 
-    if @post.errors.present?
-      render_json_error(@post)
+    if post.errors.present?
+      render_json_error(post)
       return
     end
 
-    post_serializer = PostSerializer.new(@post, scope: guardian, root: false)
-    post_serializer.draft_sequence = DraftSequence.current(current_user, @post.topic.draft_key)
-    link_counts = TopicLinkClick.counts_for(@post.topic, [@post])
-    post_serializer.single_post_link_counts = link_counts[@post.id] if link_counts.present?
-    render_json_dump(post_serializer)
+    post_serializer = PostSerializer.new(post, scope: guardian, root: false)
+    post_serializer.draft_sequence = DraftSequence.current(current_user, post.topic.draft_key)
+    link_counts = TopicLinkClick.counts_for(post.topic, [post])
+    post_serializer.single_post_link_counts = link_counts[post.id] if link_counts.present?
+
+
+    result = {post: post_serializer.as_json}
+    if revisor.category_changed.present?
+      result[:category] = CategorySerializer.new(revisor.category_changed, scope: guardian, root: false).as_json      
+    end
+
+    render_json_dump(result)
   end
 
   def by_number
diff --git a/app/models/category.rb b/app/models/category.rb
index 0088f7aef97..954b059f984 100644
--- a/app/models/category.rb
+++ b/app/models/category.rb
@@ -44,19 +44,6 @@ class Category < ActiveRecord::Base
                          topics_week = (#{topics_week})")
   end
 
-  # Use the first paragraph of the topic's first post as the excerpt
-  def excerpt
-    if topic.present?
-      first_post = topic.posts.order(:post_number).first
-      body = first_post.cooked
-      matches = body.scan(/\<p\>(.*)\<\/p\>/)
-      if matches and matches[0] and matches[0][0]
-        return matches[0][0]
-      end
-    end
-    nil
-  end
-
   def topic_url
     topic.try(:relative_url)
   end
@@ -67,11 +54,17 @@ class Category < ActiveRecord::Base
 
   after_create do
     topic = Topic.create!(title: I18n.t("category.topic_prefix", category: name), user: user, visible: false)
-    topic.posts.create!(raw: SiteSetting.category_post_template, user: user)
+
+    post_contents = I18n.t("category.post_template", replace_paragraph: I18n.t("category.replace_paragraph"))
+    topic.posts.create!(raw: post_contents, user: user)
     update_column(:topic_id, topic.id)
     topic.update_column(:category_id, self.id)
   end
 
+  def self.post_template
+    I18n.t("category.post_template", replace_paragraph: I18n.t("category.replace_paragraph"))
+  end
+
   # We cache the categories in the site json, so we need to invalidate it when they change
   def invalidate_site_cache
     Site.invalidate_cache
diff --git a/app/models/site_setting.rb b/app/models/site_setting.rb
index 82d9011b70b..91af3d223ee 100644
--- a/app/models/site_setting.rb
+++ b/app/models/site_setting.rb
@@ -100,8 +100,6 @@ class SiteSetting < ActiveRecord::Base
   setting(:best_of_score_threshold, 15)
   setting(:best_of_posts_required, 50)
   setting(:best_of_likes_required, 1)
-  setting(:category_post_template,
-          "[Replace this first paragraph with a short description of your new category. Try to keep it below 200 characters.]\n\nUse this space below for a longer description, as well as to establish any rules or discussion!")
 
   # we need to think of a way to force users to enter certain settings, this is a minimal config thing
   setting(:notification_email, 'info@discourse.org')
diff --git a/app/serializers/category_serializer.rb b/app/serializers/category_serializer.rb
index 40b459406d9..ae33145d4e6 100644
--- a/app/serializers/category_serializer.rb
+++ b/app/serializers/category_serializer.rb
@@ -1,3 +1,11 @@
 class CategorySerializer < ApplicationSerializer
-  attributes :id, :name, :color, :slug, :topic_count
+
+  attributes :id, 
+             :name, 
+             :color, 
+             :slug, 
+             :topic_count,
+             :description,
+             :topic_url
+
 end
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index eb123fa1e6e..ce7e62789b6 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -519,6 +519,7 @@ en:
     category:
       none: '(no category)'
       edit: 'edit'
+      edit_long: "Edit Category"
       view: 'View Topics in Category'
       delete: 'Delete Category'
       create: 'Create Category'
@@ -531,6 +532,8 @@ en:
       color_placeholder: "Any web color"
       delete_confirm: "Are you sure you want to delete that category?"
       list: "List Categories"
+      no_description: "There is no description for this category."
+      change_in_category_topic: "visit category topic to edit the description"
 
     flagging:
       title: 'Why are you flagging this post?'
diff --git a/config/locales/nl.yml.working b/config/locales/nl.yml.working
index 2c0572dc105..6c93abaa651 100644
--- a/config/locales/nl.yml.working
+++ b/config/locales/nl.yml.working
@@ -228,7 +228,6 @@ nl:
     exclude_rel_nofollow_domains: "Een commagescheiden lijst van domeinen waar 'nofollow' niet is toegevoegd. (voorbeelddomein.com zal automatisch sub.voorbeelddomein.com toestaan)"
     post_excerpt_maxlength: "Maximale lengte in karakters van een post-uittreksel."
     post_onebox_maxlength: "Maximale lengte van een 'oneboxed' discourse post."
-    category_post_template: "De post-template die verschijnt wanneer je een categorie aanmaakt"
     new_topics_rollup: "Hoeveel topics er aan een topic-lijst kunnen worden toegevoegd voordat de lijst oprolt."
     onebox_max_chars: "Het maximaal aantal karakters dat een 'onebox' zal importeren in een lap text."
     logo_url: "Het logo van je site bijv: http://xyz.com/x.png"
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index c8514304add..dc7f9fd0499 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -62,6 +62,8 @@ en:
 
   category:
     topic_prefix: "Category definition for %{category}"
+    replace_paragraph: "[Replace this first paragraph with a short description of your new category. Try to keep it below 200 characters.]"
+    post_template: "%{replace_paragraph}\n\nUse this space below for a longer description, as well as to establish any rules or discussion!"
 
   trust_levels:
     new:
@@ -254,7 +256,6 @@ en:
     exclude_rel_nofollow_domains: "A comma delimited list of domains where nofollow is not added (tld.com will automatically allow sub.tld.com as well)"
     post_excerpt_maxlength: "Maximum length in chars of a post's excerpt."
     post_onebox_maxlength: "Maximum length of a oneboxed discourse post."
-    category_post_template: "The post template that appears once you create a category"
     new_topics_rollup: "How many topics can be inserted on the topic list before rolling up."
     onebox_max_chars: "The maximum amount of characters a onebox will import in a text blob."
     logo_url: "The logo for your site eg: http://xyz.com/x.png"
diff --git a/config/locales/server.fr.yml b/config/locales/server.fr.yml
index e4b07ad8252..2e0749dc883 100644
--- a/config/locales/server.fr.yml
+++ b/config/locales/server.fr.yml
@@ -261,7 +261,6 @@ fr:
     exclude_rel_nofollow_domains: "Une liste séparée par des virgules contenant les noms de domaines de premier niveau pour lesquels il faut ajouter un attribut nofollow (exemple.com va automatiquement fonctionner aussi avec sous.domaine.exemple.com)"
     post_excerpt_maxlength: "Longueur maximale d'un extrait de message."
     post_onebox_maxlength: "Longueur maximale d'un message emboîté."
-    category_post_template: "Le modèle de message qui va apparaitre quand vous créez une catégorie"
     new_topics_rollup: "Combien de discussions peuvent être insérées dans la liste des discussions avant de remonter."
     onebox_max_chars: "Nombre maximal de caractères qu'une boîte peut importer en blob de texte."
     logo_url: "Le logo de votre site, par exemple: http://xyz.com/x.png"
diff --git a/config/locales/server.nl.yml b/config/locales/server.nl.yml
index fc06cd68a24..ef14d6888b5 100644
--- a/config/locales/server.nl.yml
+++ b/config/locales/server.nl.yml
@@ -255,7 +255,6 @@ nl:
     exclude_rel_nofollow_domains: "Een commagescheiden lijst van domeinen waar 'nofollow' niet is toegevoegd. (voorbeelddomein.com zal automatisch sub.voorbeelddomein.com toestaan)"
     post_excerpt_maxlength: "Maximale lengte in karakters van een post-uittreksel."
     post_onebox_maxlength: "Maximale lengte van een 'oneboxed' discourse post."
-    category_post_template: "De post-template die verschijnt wanneer je een categorie aanmaakt"
     new_topics_rollup: "Hoeveel topics er aan een topic-lijst kunnen worden toegevoegd voordat de lijst oprolt."
     onebox_max_chars: "Het maximaal aantal karakters dat een 'onebox' zal importeren in een lap tekst."
     logo_url: "Het logo van je site bijv: http://xyz.com/x.png"
diff --git a/db/migrate/20130221215017_add_description_to_categories.rb b/db/migrate/20130221215017_add_description_to_categories.rb
new file mode 100644
index 00000000000..715f7bab7ba
--- /dev/null
+++ b/db/migrate/20130221215017_add_description_to_categories.rb
@@ -0,0 +1,25 @@
+class AddDescriptionToCategories < ActiveRecord::Migration
+  def up
+    add_column :categories, :description, :text, null: true
+
+    # While we're at it, remove unused columns
+    remove_column :categories, :top1_topic_id
+    remove_column :categories, :top2_topic_id
+    remove_column :categories, :top1_user_id
+    remove_column :categories, :top2_user_id
+
+    # Migrate excerpts over
+    Category.all.each do |c| 
+      excerpt = c.excerpt
+      unless excerpt == I18n.t("category.replace_paragraph")
+        c.update_column(:description, c.excerpt)
+      end
+    end
+
+  end
+
+  def down
+    remove_column :categories, :description
+  end
+
+end
diff --git a/db/structure.sql b/db/structure.sql
index 2d3e08c02da..c95ef4865a3 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -1222,10 +1222,6 @@ CREATE TABLE categories (
     name character varying(50) NOT NULL,
     color character varying(6) DEFAULT 'AB9364'::character varying NOT NULL,
     topic_id integer,
-    top1_topic_id integer,
-    top2_topic_id integer,
-    top1_user_id integer,
-    top2_user_id integer,
     topic_count integer DEFAULT 0 NOT NULL,
     created_at timestamp without time zone NOT NULL,
     updated_at timestamp without time zone NOT NULL,
@@ -1233,7 +1229,8 @@ CREATE TABLE categories (
     topics_year integer,
     topics_month integer,
     topics_week integer,
-    slug character varying(255) NOT NULL
+    slug character varying(255) NOT NULL,
+    description text
 );
 
 
@@ -1242,7 +1239,7 @@ CREATE TABLE categories (
 --
 
 CREATE SEQUENCE categories_id_seq
-    START WITH 5
+    START WITH 1
     INCREMENT BY 1
     NO MINVALUE
     NO MAXVALUE
@@ -1327,7 +1324,7 @@ CREATE TABLE draft_sequences (
 --
 
 CREATE SEQUENCE draft_sequences_id_seq
-    START WITH 20
+    START WITH 1
     INCREMENT BY 1
     NO MINVALUE
     NO MAXVALUE
@@ -1361,7 +1358,7 @@ CREATE TABLE drafts (
 --
 
 CREATE SEQUENCE drafts_id_seq
-    START WITH 2
+    START WITH 1
     INCREMENT BY 1
     NO MINVALUE
     NO MAXVALUE
@@ -1394,7 +1391,7 @@ CREATE TABLE email_logs (
 --
 
 CREATE SEQUENCE email_logs_id_seq
-    START WITH 3
+    START WITH 1
     INCREMENT BY 1
     NO MINVALUE
     NO MAXVALUE
@@ -1429,7 +1426,7 @@ CREATE TABLE email_tokens (
 --
 
 CREATE SEQUENCE email_tokens_id_seq
-    START WITH 3
+    START WITH 1
     INCREMENT BY 1
     NO MINVALUE
     NO MAXVALUE
@@ -1642,7 +1639,7 @@ CREATE TABLE onebox_renders (
 --
 
 CREATE SEQUENCE onebox_renders_id_seq
-    START WITH 2
+    START WITH 1
     INCREMENT BY 1
     NO MINVALUE
     NO MAXVALUE
@@ -1676,7 +1673,7 @@ CREATE TABLE post_action_types (
 --
 
 CREATE SEQUENCE post_action_types_id_seq
-    START WITH 6
+    START WITH 1
     INCREMENT BY 1
     NO MINVALUE
     NO MAXVALUE
@@ -1807,7 +1804,7 @@ CREATE TABLE posts (
 --
 
 CREATE SEQUENCE posts_id_seq
-    START WITH 16
+    START WITH 1
     INCREMENT BY 1
     NO MINVALUE
     NO MAXVALUE
@@ -1898,7 +1895,7 @@ CREATE TABLE site_settings (
 --
 
 CREATE SEQUENCE site_settings_id_seq
-    START WITH 4
+    START WITH 1
     INCREMENT BY 1
     NO MINVALUE
     NO MAXVALUE
@@ -1930,7 +1927,7 @@ CREATE TABLE topic_allowed_users (
 --
 
 CREATE SEQUENCE topic_allowed_users_id_seq
-    START WITH 3
+    START WITH 1
     INCREMENT BY 1
     NO MINVALUE
     NO MAXVALUE
@@ -2122,7 +2119,7 @@ CREATE TABLE topics (
 --
 
 CREATE SEQUENCE topics_id_seq
-    START WITH 16
+    START WITH 1
     INCREMENT BY 1
     NO MINVALUE
     NO MAXVALUE
@@ -2228,7 +2225,7 @@ CREATE TABLE user_actions (
 --
 
 CREATE SEQUENCE user_actions_id_seq
-    START WITH 40
+    START WITH 1
     INCREMENT BY 1
     NO MINVALUE
     NO MAXVALUE
@@ -2292,7 +2289,7 @@ CREATE TABLE user_visits (
 --
 
 CREATE SEQUENCE user_visits_id_seq
-    START WITH 4
+    START WITH 1
     INCREMENT BY 1
     NO MINVALUE
     NO MAXVALUE
@@ -2359,7 +2356,7 @@ CREATE TABLE users (
 --
 
 CREATE SEQUENCE users_id_seq
-    START WITH 3
+    START WITH 1
     INCREMENT BY 1
     NO MINVALUE
     NO MAXVALUE
@@ -4578,4 +4575,6 @@ INSERT INTO schema_migrations (version) VALUES ('20130208220635');
 
 INSERT INTO schema_migrations (version) VALUES ('20130213021450');
 
-INSERT INTO schema_migrations (version) VALUES ('20130213203300');
\ No newline at end of file
+INSERT INTO schema_migrations (version) VALUES ('20130213203300');
+
+INSERT INTO schema_migrations (version) VALUES ('20130221215017');
\ No newline at end of file
diff --git a/lib/post_revisor.rb b/lib/post_revisor.rb
index c8ec6449eac..4eb7656fc36 100644
--- a/lib/post_revisor.rb
+++ b/lib/post_revisor.rb
@@ -1,5 +1,8 @@
 require 'edit_rate_limiter'
 class PostRevisor
+
+  attr_reader :category_changed
+
   def initialize(post)
     @post = post
   end
@@ -8,6 +11,7 @@ class PostRevisor
     @user, @new_raw, @opts = user, new_raw, opts
     return false if not should_revise?
     revise_post
+    update_category_description
     post_process_post
     true
   end
@@ -76,6 +80,25 @@ class PostRevisor
     @post.save
   end
 
+  def update_category_description
+    # If we're revising the first post, we might have to update the category description
+    return unless @post.post_number == 1
+
+    # Is there a category with our topic id?
+    category = Category.where(topic_id: @post.topic_id).first
+    return unless category.present?
+
+    # If found, update its description
+    body = @post.cooked
+    matches = body.scan(/\<p\>(.*)\<\/p\>/)
+    if matches and matches[0] and matches[0][0]      
+      new_description = matches[0][0]
+      new_description = nil if new_description == I18n.t("category.replace_paragraph")
+      category.update_column(:description, new_description)
+      @category_changed = category
+    end
+  end
+
   def post_process_post
     @post.invalidate_oneboxes = true
     @post.trigger_post_process
diff --git a/spec/components/post_revisor_spec.rb b/spec/components/post_revisor_spec.rb
index 386211f472a..36662d9cd99 100644
--- a/spec/components/post_revisor_spec.rb
+++ b/spec/components/post_revisor_spec.rb
@@ -43,6 +43,11 @@ describe PostRevisor do
       it "doesn't change the last_version_at" do
         post.last_version_at.should == first_version_at
       end
+
+      it "doesn't update a category" do
+        subject.category_changed.should be_blank
+      end
+
     end
 
     describe 'revision much later' do
@@ -55,6 +60,10 @@ describe PostRevisor do
         post.reload
       end
 
+      it "doesn't update a category" do
+        subject.category_changed.should be_blank
+      end
+
       it 'updates the cached_version' do
         post.cached_version.should == 2
       end
@@ -82,6 +91,10 @@ describe PostRevisor do
           post.last_version_at.to_i.should == revised_at.to_i
         end
 
+        it "doesn't update a category" do
+          subject.category_changed.should be_blank
+        end
+
         context "after second window" do
 
           let!(:new_revised_at) {revised_at + 2.minutes}
@@ -102,6 +115,68 @@ describe PostRevisor do
       end
     end
 
+    describe 'category topic' do
+
+      let!(:category) do 
+        category = Fabricate(:category)
+        category.update_column(:topic_id, topic.id)
+        category
+      end
+
+      let(:new_description) { "this is my new description." }
+
+      it "should have to description by default" do
+        category.description.should be_blank
+      end
+
+      context "one paragraph description" do
+        before do
+          subject.revise!(post.user, new_description)
+          category.reload
+        end
+
+        it "returns true for category_changed" do
+          subject.category_changed.should be_true
+        end
+
+        it "updates the description of the category" do
+          category.description.should == new_description
+        end
+      end
+
+      context "multiple paragraph description" do
+        before do
+          subject.revise!(post.user, "#{new_description}\n\nOther content goes here.")
+          category.reload
+        end
+
+        it "returns the changed category info" do
+          subject.category_changed.should == category
+        end
+
+        it "updates the description of the category" do
+          category.description.should == new_description
+        end        
+      end
+
+      context 'when updating back to the original paragraph' do
+        before do
+          category.update_column(:description, 'this is my description')
+          subject.revise!(post.user, Category.post_template)
+          category.reload
+        end
+
+        it "puts the description back to nothing" do
+          category.description.should be_blank
+        end
+
+        it "returns true for category_changed" do
+          subject.category_changed.should == category
+        end
+      end
+
+    end
+
     describe 'rate limiter' do
       let(:changed_by) { Fabricate(:coding_horror) }
 
diff --git a/spec/controllers/posts_controller_spec.rb b/spec/controllers/posts_controller_spec.rb
index 73929f8b1a0..88c5d85cef0 100644
--- a/spec/controllers/posts_controller_spec.rb
+++ b/spec/controllers/posts_controller_spec.rb
@@ -192,7 +192,7 @@ describe PostsController do
       end
 
       it "calls revise with valid parameters" do
-        Post.any_instance.expects(:revise).with(post.user, 'edited body')
+        PostRevisor.any_instance.expects(:revise!).with(post.user, 'edited body')
         xhr :put, :update, update_params
       end
 
diff --git a/spec/models/category_spec.rb b/spec/models/category_spec.rb
index b81b9c757d0..9f1c713e5d6 100644
--- a/spec/models/category_spec.rb
+++ b/spec/models/category_spec.rb
@@ -83,6 +83,10 @@ describe Category do
       @category.slug.should == 'amazing-category'
     end
 
+    it 'has a default description' do
+      @category.description.should be_blank
+    end
+
     it 'has one topic' do
       Topic.where(category_id: @category).count.should == 1
     end
@@ -107,14 +111,12 @@ describe Category do
       @topic.posts.count.should == 1
     end
 
-    it 'should have an excerpt' do
-      @category.excerpt.should be_present
-    end
-
     it 'should have a topic url' do
       @category.topic_url.should be_present
     end
 
+
+
     describe "trying to change the category topic's category" do
 
       before do
@@ -166,8 +168,7 @@ describe Category do
     context 'with regular topics' do
 
       before do
-        @category.topics << Fabricate(:topic, 
-                                      user: @category.user)     
+        @category.topics << Fabricate(:topic, user: @category.user)     
         Category.update_stats
         @category.reload
       end