FEATURE: offer to unwatch categories when unwatching category

This commit is contained in:
Sam 2016-06-28 18:34:20 +10:00
parent 3e07658fb2
commit 1411eedad3
6 changed files with 113 additions and 2 deletions

View File

@ -2,6 +2,7 @@ import { setting } from 'discourse/lib/computed';
import CanCheckEmails from 'discourse/mixins/can-check-emails'; import CanCheckEmails from 'discourse/mixins/can-check-emails';
import { popupAjaxError } from 'discourse/lib/ajax-error'; import { popupAjaxError } from 'discourse/lib/ajax-error';
import computed from "ember-addons/ember-computed-decorators"; import computed from "ember-addons/ember-computed-decorators";
import { categoryBadgeHTML } from "discourse/helpers/category-link";
export default Ember.Controller.extend(CanCheckEmails, { export default Ember.Controller.extend(CanCheckEmails, {
@ -134,6 +135,25 @@ export default Ember.Controller.extend(CanCheckEmails, {
this.set('saved', false); this.set('saved', false);
const model = this.get('model'); const model = this.get('model');
// watched status changes warn user
const changedWatch = model.changedCategoryNotifications("watched");
if (changedWatch.remove.length > 0 && !this.get("warnedRemoveWatch")) {
var categories = Discourse.Category.findByIds(changedWatch.remove).map((cat) => {
return categoryBadgeHTML(cat);
}).join(" ");
bootbox.confirm(I18n.t('user.warn_unwatch.message', {categories: categories}),
I18n.t('user.warn_unwatch.no_value', {count: changedWatch.remove.length}), I18n.t('user.warn_unwatch.yes_value'),
(yes)=>{
this.set('unwatchCategoryTopics', yes ? changedWatch.remove : false);
this.send('save');
});
this.set("warnedRemoveWatch", true);
return;
}
const userFields = this.get('userFields'); const userFields = this.get('userFields');
// Update the user fields // Update the user fields
@ -148,12 +168,19 @@ export default Ember.Controller.extend(CanCheckEmails, {
// Cook the bio for preview // Cook the bio for preview
model.set('name', this.get('newNameInput')); model.set('name', this.get('newNameInput'));
return model.save().then(() => { var options = {};
if (this.get('warnedRemoveWatch') && this.get('unwatchCategoryTopics')) {
options["unwatchCategoryTopics"] = this.get("unwatchCategoryTopics");
}
return model.save(options).then(() => {
if (Discourse.User.currentProp('id') === model.get('id')) { if (Discourse.User.currentProp('id') === model.get('id')) {
Discourse.User.currentProp('name', model.get('name')); Discourse.User.currentProp('name', model.get('name'));
} }
model.set('bio_cooked', Discourse.Markdown.cook(Discourse.Markdown.sanitize(model.get('bio_raw')))); model.set('bio_cooked', Discourse.Markdown.cook(Discourse.Markdown.sanitize(model.get('bio_raw'))));
this.set('saved', true); this.set('saved', true);
this.set("unwatchTopics", false);
this.set('warnedRemoveWatch', false);
}).catch(popupAjaxError); }).catch(popupAjaxError);
}, },

View File

@ -141,7 +141,7 @@ const User = RestModel.extend({
return Discourse.User.create(this.getProperties(Object.keys(this))); return Discourse.User.create(this.getProperties(Object.keys(this)));
}, },
save() { save(options) {
const data = this.getProperties( const data = this.getProperties(
'bio_raw', 'bio_raw',
'website', 'website',
@ -177,8 +177,12 @@ const User = RestModel.extend({
data[s] = this.get(`user_option.${s}`); data[s] = this.get(`user_option.${s}`);
}); });
var updatedState = {};
['muted','watched','tracked'].forEach(s => { ['muted','watched','tracked'].forEach(s => {
let cats = this.get(s + 'Categories').map(c => c.get('id')); let cats = this.get(s + 'Categories').map(c => c.get('id'));
updatedState[s + '_category_ids'] = cats;
// HACK: denote lack of categories // HACK: denote lack of categories
if (cats.length === 0) { cats = [-1]; } if (cats.length === 0) { cats = [-1]; }
data[s + '_category_ids'] = cats; data[s + '_category_ids'] = cats;
@ -188,6 +192,10 @@ const User = RestModel.extend({
data['edit_history_public'] = this.get('user_option.edit_history_public'); data['edit_history_public'] = this.get('user_option.edit_history_public');
} }
if (options && options.unwatchCategoryTopics) {
data.unwatch_category_topics = options.unwatchCategoryTopics;
}
// TODO: We can remove this when migrated fully to rest model. // TODO: We can remove this when migrated fully to rest model.
this.set('isSaving', true); this.set('isSaving', true);
return Discourse.ajax(`/users/${this.get('username_lower')}`, { return Discourse.ajax(`/users/${this.get('username_lower')}`, {
@ -197,6 +205,7 @@ const User = RestModel.extend({
this.set('bio_excerpt', result.user.bio_excerpt); this.set('bio_excerpt', result.user.bio_excerpt);
const userProps = Em.getProperties(this.get('user_option'),'enable_quoting', 'external_links_in_new_tab', 'dynamic_favicon'); const userProps = Em.getProperties(this.get('user_option'),'enable_quoting', 'external_links_in_new_tab', 'dynamic_favicon');
Discourse.User.current().setProperties(userProps); Discourse.User.current().setProperties(userProps);
this.setProperties(updatedState);
}).finally(() => { }).finally(() => {
this.set('isSaving', false); this.set('isSaving', false);
}); });
@ -352,6 +361,16 @@ const User = RestModel.extend({
this.set("watchedCategories", Discourse.Category.findByIds(this.watched_category_ids)); this.set("watchedCategories", Discourse.Category.findByIds(this.watched_category_ids));
}, },
changedCategoryNotifications: function(type) {
const ids = this.get(type + "Categories").map(c => c.id);
const oldIds = this.get(type + "_category_ids");
return {
add: _.difference(ids, oldIds),
remove: _.difference(oldIds, ids),
}
},
@computed("can_delete_account", "reply_count", "topic_count") @computed("can_delete_account", "reply_count", "topic_count")
canDeleteAccount(canDeleteAccount, replyCount, topicCount) { canDeleteAccount(canDeleteAccount, replyCount, topicCount) {
return !Discourse.SiteSettings.enable_sso && canDeleteAccount && ((replyCount || 0) + (topicCount || 0)) <= 1; return !Discourse.SiteSettings.enable_sso && canDeleteAccount && ((replyCount || 0) + (topicCount || 0)) <= 1;

View File

@ -89,6 +89,10 @@ class UsersController < ApplicationController
user = fetch_user_from_params user = fetch_user_from_params
guardian.ensure_can_edit!(user) guardian.ensure_can_edit!(user)
if params[:unwatch_category_topics]
TopicUser.unwatch_categories!(user, params[:unwatch_category_topics])
end
if params[:user_fields].present? if params[:user_fields].present?
params[:custom_fields] = {} unless params[:custom_fields].present? params[:custom_fields] = {} unless params[:custom_fields].present?

View File

@ -59,6 +59,32 @@ class TopicUser < ActiveRecord::Base
topic_user.save topic_user.save
end end
def unwatch_categories!(user, category_ids)
track_threshold = user.user_option.auto_track_topics_after_msecs
sql = <<SQL
UPDATE topic_users tu
SET notification_level = CASE
WHEN t.user_id = :user_id THEN :watching
WHEN total_msecs_viewed > :track_threshold AND :track_threshold >= 0 THEN :tracking
ELSE :regular
end
FROM topics t
WHERE t.id = tu.topic_id AND tu.notification_level <> :muted AND category_id IN (:category_ids) AND tu.user_id = :user_id
SQL
exec_sql(sql,
watching: notification_levels[:watching],
tracking: notification_levels[:tracking],
regular: notification_levels[:regular],
muted: notification_levels[:muted],
category_ids: category_ids,
user_id: user.id,
track_threshold: track_threshold
)
end
# Find the information specific to a user in a forum topic # Find the information specific to a user in a forum topic
def lookup_for(user, topics) def lookup_for(user, topics)
# If the user isn't logged in, there's no last read posts # If the user isn't logged in, there's no last read posts

View File

@ -574,6 +574,13 @@ en:
failed_to_move: "Failed to move selected messages (perhaps your network is down)" failed_to_move: "Failed to move selected messages (perhaps your network is down)"
select_all: "Select All" select_all: "Select All"
warn_unwatch:
message: "Also stop watching previously watched topics in {{categories}}?"
yes_value: "Yes, unwatch topics"
no_value:
one: "No, only unwatch category"
other: "No, only unwatch categories"
change_password: change_password:
success: "(email sent)" success: "(email sent)"
in_progress: "(sending email)" in_progress: "(sending email)"

View File

@ -2,6 +2,34 @@ require 'rails_helper'
describe TopicUser do describe TopicUser do
describe "#unwatch_categories!" do
it "correctly unwatches categories" do
op_topic = Fabricate(:topic)
another_topic = Fabricate(:topic)
tracked_topic = Fabricate(:topic)
user = op_topic.user
watching = TopicUser.notification_levels[:watching]
regular = TopicUser.notification_levels[:regular]
tracking = TopicUser.notification_levels[:tracking]
TopicUser.change(user.id, op_topic, notification_level: watching)
TopicUser.change(user.id, another_topic, notification_level: watching)
TopicUser.change(user.id, tracked_topic, notification_level: watching, total_msecs_viewed: SiteSetting.default_other_auto_track_topics_after_msecs + 1)
TopicUser.unwatch_categories!(user, [Fabricate(:category).id, Fabricate(:category).id])
expect(TopicUser.get(another_topic, user).notification_level).to eq(watching)
TopicUser.unwatch_categories!(user, [op_topic.category_id])
expect(TopicUser.get(op_topic, user).notification_level).to eq(watching)
expect(TopicUser.get(another_topic, user).notification_level).to eq(regular)
expect(TopicUser.get(tracked_topic, user).notification_level).to eq(tracking)
end
end
describe '#notification_levels' do describe '#notification_levels' do
context "verify enum sequence" do context "verify enum sequence" do
before do before do