diff --git a/app/assets/javascripts/discourse/components/category_group_component.js b/app/assets/javascripts/discourse/components/category_group_component.js
new file mode 100644
index 00000000000..28cc4ea4b72
--- /dev/null
+++ b/app/assets/javascripts/discourse/components/category_group_component.js
@@ -0,0 +1,42 @@
+Discourse.CategoryGroupComponent = Ember.Component.extend({
+
+ didInsertElement: function(){
+ var self = this;
+
+ this.$('input').autocomplete({
+ items: this.get('categories'),
+ single: false,
+ allowAny: false,
+ dataSource: function(term){
+ return Discourse.Category.list().filter(function(category){
+ var regex = new RegExp(term, "i");
+ return category.get("name").match(regex) &&
+ !_.contains(self.get('categories'), category);
+ });
+ },
+ onChangeItems: function(items) {
+ self.set("categories", items);
+ },
+ template: Discourse.CategoryGroupComponent.templateFunction(),
+ transformComplete: function(category){
+ return Discourse.HTML.categoryLink(category);
+ }
+ });
+ }
+
+});
+
+Discourse.CategoryGroupComponent.reopenClass({
+ templateFunction: function(){
+ this.compiled = this.compiled || Handlebars.compile("
diff --git a/app/assets/stylesheets/desktop/compose.scss b/app/assets/stylesheets/desktop/compose.scss
index 30a63b3fad4..f8be996d397 100644
--- a/app/assets/stylesheets/desktop/compose.scss
+++ b/app/assets/stylesheets/desktop/compose.scss
@@ -387,7 +387,7 @@ div.ac-wrap {
line-height: 22px;
vertical-align: bottom;
}
- a {
+ a.remove {
margin-left: 4px;
font-size: 10px;
line-height: 10px;
diff --git a/app/assets/stylesheets/desktop/user.scss b/app/assets/stylesheets/desktop/user.scss
index 8d7040391ab..445e96461ca 100644
--- a/app/assets/stylesheets/desktop/user.scss
+++ b/app/assets/stylesheets/desktop/user.scss
@@ -3,6 +3,19 @@
@import "common/foundation/mixins";
.user-preferences {
+ input.category-group {
+ width: 500px;
+ }
+
+ .autocomplete .badge-category {
+ margin: 2px;
+ font-weight: normal;
+ }
+
+ .autocomplete .badge-category.selected {
+ font-weight: bold;
+ }
+
textarea {
width: 530px;
height: 100px;
diff --git a/app/models/category_user.rb b/app/models/category_user.rb
index aef7760284c..ea59fa3c0bf 100644
--- a/app/models/category_user.rb
+++ b/app/models/category_user.rb
@@ -2,6 +2,10 @@ class CategoryUser < ActiveRecord::Base
belongs_to :category
belongs_to :user
+ def self.lookup(user, level)
+ self.where(user: user, notification_level: notification_levels[level])
+ end
+
# same for now
def self.notification_levels
TopicUser.notification_levels
@@ -15,6 +19,21 @@ class CategoryUser < ActiveRecord::Base
)
end
+ def self.batch_set(user, level, category_ids)
+ records = CategoryUser.where(user: user, notification_level: notification_levels[level])
+
+ old_ids = records.pluck(:category_id)
+
+ remove = (old_ids - category_ids)
+ if remove.present?
+ records.where('category_id in (?)', remove).destroy_all
+ end
+
+ (category_ids - old_ids).each do |id|
+ CategoryUser.create!(user: user, category_id: id, notification_level: notification_levels[level])
+ end
+ end
+
def self.auto_mute_new_topic(topic)
apply_default_to_topic(
topic,
@@ -23,6 +42,15 @@ class CategoryUser < ActiveRecord::Base
)
end
+ def notification_level1=(val)
+ val = Symbol === val ? CategoryUser.notification_levels[val] : val
+ attributes[:notification_level] = val
+ end
+
+ def notification_level1
+ attributes[:notification_level]
+ end
+
private
def self.apply_default_to_topic(topic, level, reason)
diff --git a/app/serializers/user_serializer.rb b/app/serializers/user_serializer.rb
index d2830a9d21f..56a4af44510 100644
--- a/app/serializers/user_serializer.rb
+++ b/app/serializers/user_serializer.rb
@@ -60,7 +60,9 @@ class UserSerializer < BasicUserSerializer
:use_uploaded_avatar,
:has_uploaded_avatar,
:gravatar_template,
- :uploaded_avatar_template
+ :uploaded_avatar_template,
+ :muted_category_ids,
+ :watched_category_ids
def auto_track_topics_after_msecs
@@ -101,8 +103,16 @@ class UserSerializer < BasicUserSerializer
def include_suspend_reason?
object.suspended?
end
+
def include_suspended_till?
object.suspended?
end
+ def muted_category_ids
+ CategoryUser.lookup(object, :muted).pluck(:category_id)
+ end
+
+ def watched_category_ids
+ CategoryUser.lookup(object, :watching).pluck(:category_id)
+ end
end
diff --git a/app/services/user_updater.rb b/app/services/user_updater.rb
index 6066f378ae5..6fbffd03c78 100644
--- a/app/services/user_updater.rb
+++ b/app/services/user_updater.rb
@@ -11,8 +11,16 @@ class UserUpdater
user.name = attributes[:name] || user.name
user.digest_after_days = attributes[:digest_after_days] || user.digest_after_days
+ if ids = attributes[:watched_category_ids]
+ CategoryUser.batch_set(user, :watching, ids)
+ end
+
+ if ids = attributes[:muted_category_ids]
+ CategoryUser.batch_set(user, :muted, ids)
+ end
+
if attributes[:auto_track_topics_after_msecs]
- user.auto_track_topics_after_msecs = attributes[:auto_track_topics_after_msecs].to_i
+ user.auto_track_topics_after_msecs = attributes[:auto_track_topics_after_msecs].to_i
end
if attributes[:new_topic_duration_minutes]
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 35971134597..21c9292ca92 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -228,6 +228,10 @@ en:
suspended_notice: "This user is suspended until {{date}}."
suspended_reason: "Reason: "
watch_new_topics: "Automatically watch all new topics posted on the forum"
+ watched_categories: "Watched"
+ watched_categories_instructions: "You will automatically watch all topics in these categories"
+ muted_categories: "Muted"
+ muted_categories_instructions: "You will automatically mute all topics in these categories"
messages:
all: "All"
@@ -313,6 +317,7 @@ en:
email_always: "Receive email notifications and email digests even if I am active on the forum"
other_settings: "Other"
+ categories_settings: "Categories"
new_topic_duration:
label: "Consider topics new when"
diff --git a/spec/models/category_user_spec.rb b/spec/models/category_user_spec.rb
index aff454adfd5..ab64fcaba4a 100644
--- a/spec/models/category_user_spec.rb
+++ b/spec/models/category_user_spec.rb
@@ -4,6 +4,25 @@ require 'spec_helper'
require_dependency 'post_creator'
describe CategoryUser do
+
+ it 'allows batch set' do
+ user = Fabricate(:user)
+ category1 = Fabricate(:category)
+ category2 = Fabricate(:category)
+
+ watching = CategoryUser.where(user_id: user.id, notification_level: CategoryUser.notification_levels[:watching])
+
+ CategoryUser.batch_set(user, :watching, [category1.id, category2.id])
+ watching.pluck(:category_id).sort.should == [category1.id, category2.id]
+
+ CategoryUser.batch_set(user, :watching, [])
+ watching.count.should == 0
+
+ CategoryUser.batch_set(user, :watching, [category2.id])
+ watching.count.should == 1
+ end
+
+
context 'integration' do
before do
ActiveRecord::Base.observers.enable :all