diff --git a/app/assets/javascripts/discourse/mixins/bulk-topic-selection.js.es6 b/app/assets/javascripts/discourse/mixins/bulk-topic-selection.js.es6 index 7dd6331ba7e..411dfb6688a 100644 --- a/app/assets/javascripts/discourse/mixins/bulk-topic-selection.js.es6 +++ b/app/assets/javascripts/discourse/mixins/bulk-topic-selection.js.es6 @@ -18,7 +18,7 @@ export default Ember.Mixin.create({ this.selected.clear(); }, - dismissRead(operationType) { + dismissRead(operationType, categoryOptions) { let operation; if (operationType === "posts") { operation = { type: "dismiss_posts" }; @@ -36,7 +36,8 @@ export default Ember.Mixin.create({ promise = Discourse.Topic.bulkOperationByFilter( "unread", operation, - this.get("category.id") + this.get("category.id"), + categoryOptions ); } diff --git a/app/assets/javascripts/discourse/models/topic.js.es6 b/app/assets/javascripts/discourse/models/topic.js.es6 index a86a33303f1..bc537173ca5 100644 --- a/app/assets/javascripts/discourse/models/topic.js.es6 +++ b/app/assets/javascripts/discourse/models/topic.js.es6 @@ -733,8 +733,13 @@ Topic.reopenClass({ }); }, - bulkOperationByFilter(filter, operation, categoryId) { - const data = { filter, operation }; + bulkOperationByFilter(filter, operation, categoryId, options) { + let data = { filter, operation }; + + if (options && options.includeSubcategories) { + data.include_subcategories = true; + } + if (categoryId) data.category_id = categoryId; return ajax("/topics/bulk", { type: "PUT", diff --git a/app/assets/javascripts/discourse/routes/discovery.js.es6 b/app/assets/javascripts/discourse/routes/discovery.js.es6 index 32926561b6c..234ef159303 100644 --- a/app/assets/javascripts/discourse/routes/discovery.js.es6 +++ b/app/assets/javascripts/discourse/routes/discovery.js.es6 @@ -60,12 +60,15 @@ export default Discourse.Route.extend(OpenComposer, { }, dismissReadTopics(dismissTopics) { - var operationType = dismissTopics ? "topics" : "posts"; - this.controllerFor("discovery/topics").send("dismissRead", operationType); + const operationType = dismissTopics ? "topics" : "posts"; + this.send("dismissRead", operationType); }, dismissRead(operationType) { - this.controllerFor("discovery/topics").send("dismissRead", operationType); + const controller = this.controllerFor("discovery/topics"); + controller.send("dismissRead", operationType, { + includeSubcategories: !controller.noSubcategories + }); } } }); diff --git a/app/controllers/topics_controller.rb b/app/controllers/topics_controller.rb index f578291f146..1535359429e 100644 --- a/app/controllers/topics_controller.rb +++ b/app/controllers/topics_controller.rb @@ -779,7 +779,17 @@ class TopicsController < ApplicationController elsif params[:filter] == 'unread' tq = TopicQuery.new(current_user) topics = TopicQuery.unread_filter(tq.joined_topic_user, current_user.id, staff: guardian.is_staff?).listable_topics - topics = topics.where('category_id = ?', params[:category_id]) if params[:category_id] + + if params[:category_id] + if params[:include_subcategories] + topics = topics.where(<<~SQL, category_id: params[:category_id]) + category_id in (select id FROM categories WHERE parent_category_id = :category_id) OR + category_id = :category_id + SQL + else + topics = topics.where('category_id = ?', params[:category_id]) + end + end topic_ids = topics.pluck(:id) else raise ActionController::ParameterMissing.new(:topic_ids) diff --git a/lib/topics_bulk_action.rb b/lib/topics_bulk_action.rb index 207f2f0259f..a26dc0eb0ae 100644 --- a/lib/topics_bulk_action.rb +++ b/lib/topics_bulk_action.rb @@ -68,12 +68,12 @@ class TopicsBulkAction end def dismiss_posts - sql = " - UPDATE topic_users tu - SET highest_seen_post_number = t.highest_post_number , last_read_post_number = highest_post_number - FROM topics t - WHERE t.id = tu.topic_id AND tu.user_id = :user_id AND t.id IN (:topic_ids) - " + sql = <<~SQL + UPDATE topic_users tu + SET highest_seen_post_number = t.highest_post_number , last_read_post_number = highest_post_number + FROM topics t + WHERE t.id = tu.topic_id AND tu.user_id = :user_id AND t.id IN (:topic_ids) + SQL DB.exec(sql, user_id: @user.id, topic_ids: @topic_ids) @changed_ids.concat @topic_ids diff --git a/spec/requests/topics_controller_spec.rb b/spec/requests/topics_controller_spec.rb index 6aac3a99ace..758bda7ce83 100644 --- a/spec/requests/topics_controller_spec.rb +++ b/spec/requests/topics_controller_spec.rb @@ -2039,6 +2039,27 @@ RSpec.describe TopicsController do expect(response.status).to eq(400) end + it "can mark sub-categories unread" do + # TODO do we want to skip category definition by default in fabricator + category = Fabricate(:category, skip_category_definition: true) + sub = Fabricate(:category, parent_category_id: category.id, skip_category_definition: true) + + topic.update!(category_id: sub.id) + + post1 = create_post(user: user, topic_id: topic.id) + create_post(topic_id: topic.id) + + put "/topics/bulk.json", params: { + category_id: category.id, + include_subcategories: true, + filter: 'unread', + operation: { type: 'dismiss_posts' } + } + + expect(response.status).to eq(200) + expect(TopicUser.get(post1.topic, post1.user).last_read_post_number).to eq(2) + end + it "can find unread" do # mark all unread muted put "/topics/bulk.json", params: {