diff --git a/app/assets/javascripts/discourse/app/components/bulk-select-button.js b/app/assets/javascripts/discourse/app/components/bulk-select-button.js
index 9f46112d23d..e3359e1f738 100644
--- a/app/assets/javascripts/discourse/app/components/bulk-select-button.js
+++ b/app/assets/javascripts/discourse/app/components/bulk-select-button.js
@@ -1,5 +1,6 @@
import Component from "@ember/component";
import { schedule } from "@ember/runloop";
+import { reads } from "@ember/object/computed";
import showModal from "discourse/lib/show-modal";
export default Component.extend({
@@ -17,6 +18,8 @@ export default Component.extend({
});
},
+ canDoBulkActions: reads("currentUser.staff"),
+
actions: {
showBulkActions() {
const controller = showModal("topic-bulk-actions", {
diff --git a/app/assets/javascripts/discourse/app/components/topic-dismiss-buttons.js b/app/assets/javascripts/discourse/app/components/topic-dismiss-buttons.js
new file mode 100644
index 00000000000..5092bfe2df8
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/components/topic-dismiss-buttons.js
@@ -0,0 +1,105 @@
+import { action } from "@ember/object";
+import showModal from "discourse/lib/show-modal";
+import { later } from "@ember/runloop";
+import isElementInViewport from "discourse/lib/is-element-in-viewport";
+import discourseComputed, { on } from "discourse-common/utils/decorators";
+import I18n from "I18n";
+import Component from "@ember/component";
+
+export default Component.extend({
+ tagName: "",
+ classNames: ["topic-dismiss-buttons"],
+
+ position: null,
+ selectedTopics: null,
+ model: null,
+
+ @discourseComputed("position")
+ containerClass(position) {
+ return `dismiss-container-${position}`;
+ },
+
+ @discourseComputed("position")
+ dismissReadId(position) {
+ return `dismiss-topics-${position}`;
+ },
+
+ @discourseComputed("position")
+ dismissNewId(position) {
+ return `dismiss-new-${position}`;
+ },
+
+ @discourseComputed(
+ "position",
+ "isOtherDismissUnreadButtonVisible",
+ "isOtherDismissNewButtonVisible"
+ )
+ showBasedOnPosition(
+ position,
+ isOtherDismissUnreadButtonVisible,
+ isOtherDismissNewButtonVisible
+ ) {
+ if (position !== "top") {
+ return true;
+ }
+
+ return !(
+ isOtherDismissUnreadButtonVisible || isOtherDismissNewButtonVisible
+ );
+ },
+
+ @discourseComputed("selectedTopics.length")
+ dismissLabel(selectedTopicCount) {
+ if (selectedTopicCount === 0) {
+ return I18n.t("topics.bulk.dismiss_button");
+ }
+ return I18n.t("topics.bulk.dismiss_button_with_selected", {
+ count: selectedTopicCount,
+ });
+ },
+
+ @discourseComputed("selectedTopics.length")
+ dismissNewLabel(selectedTopicCount) {
+ if (selectedTopicCount === 0) {
+ return I18n.t("topics.bulk.dismiss_new");
+ }
+ return I18n.t("topics.bulk.dismiss_new_with_selected", {
+ count: selectedTopicCount,
+ });
+ },
+
+ // we want to only render the Dismiss... button at the top of the
+ // page if the user cannot see the bottom Dismiss... button based on their
+ // viewport, or if too many topics fill the page
+ @on("didInsertElement")
+ _determineOtherDismissVisibility() {
+ later(() => {
+ if (this.position === "top") {
+ this.set(
+ "isOtherDismissUnreadButtonVisible",
+ isElementInViewport(document.getElementById("dismiss-topics-bottom"))
+ );
+ this.set(
+ "isOtherDismissNewButtonVisible",
+ isElementInViewport(document.getElementById("dismiss-new-bottom"))
+ );
+ } else {
+ this.set("isOtherDismissUnreadButtonVisible", true);
+ this.set("isOtherDismissNewButtonVisible", true);
+ }
+ });
+ },
+
+ @action
+ dismissReadPosts() {
+ let dismissTitle = "topics.bulk.dismiss_read";
+ if (this.selectedTopics.length > 0) {
+ dismissTitle = "topics.bulk.dismiss_read_with_selected";
+ }
+ showModal("dismiss-read", {
+ titleTranslated: I18n.t(dismissTitle, {
+ count: this.selectedTopics.length,
+ }),
+ });
+ },
+});
diff --git a/app/assets/javascripts/discourse/app/components/watch-read.js b/app/assets/javascripts/discourse/app/components/watch-read.js
index 66030689805..e375f4e183e 100644
--- a/app/assets/javascripts/discourse/app/components/watch-read.js
+++ b/app/assets/javascripts/discourse/app/components/watch-read.js
@@ -13,7 +13,7 @@ export default Component.extend({
if (path === "faq" || path === "guidelines") {
$(window).on("load.faq resize.faq scroll.faq", () => {
const faqUnread = !currentUser.get("read_faq");
- if (faqUnread && isElementInViewport($(".contents p").last())) {
+ if (faqUnread && isElementInViewport($(".contents p").last()[0])) {
this.action();
}
});
diff --git a/app/assets/javascripts/discourse/app/controllers/discovery/topics.js b/app/assets/javascripts/discourse/app/controllers/discovery/topics.js
index 6e33eb20fce..99afc1dedab 100644
--- a/app/assets/javascripts/discourse/app/controllers/discovery/topics.js
+++ b/app/assets/javascripts/discourse/app/controllers/discovery/topics.js
@@ -18,7 +18,6 @@ import discourseComputed from "discourse-common/utils/decorators";
import { endWith } from "discourse/lib/computed";
import { routeAction } from "discourse/helpers/route-action";
import { inject as service } from "@ember/service";
-import showModal from "discourse/lib/show-modal";
import { userPath } from "discourse/lib/url";
const controllerOpts = {
@@ -39,6 +38,18 @@ const controllerOpts = {
order: readOnly("model.params.order"),
ascending: readOnly("model.params.ascending"),
+ selected: null,
+
+ @discourseComputed("model.filter", "model.topics.length")
+ showDismissRead(filter, topicsLength) {
+ return this._isFilterPage(filter, "unread") && topicsLength > 0;
+ },
+
+ @discourseComputed("model.filter", "model.topics.length")
+ showResetNew(filter, topicsLength) {
+ return this._isFilterPage(filter, "new") && topicsLength > 0;
+ },
+
actions: {
changeSort() {
deprecated(
@@ -98,17 +109,20 @@ const controllerOpts = {
(this.router.currentRoute.queryParams["f"] ||
this.router.currentRoute.queryParams["filter"]) === "tracked";
- Topic.resetNew(this.category, !this.noSubcategories, tracked).then(() =>
+ let topicIds = this.selected
+ ? this.selected.map((topic) => topic.id)
+ : null;
+
+ Topic.resetNew(this.category, !this.noSubcategories, {
+ tracked,
+ topicIds,
+ }).then(() =>
this.send(
"refresh",
tracked ? { skipResettingParams: ["filter", "f"] } : {}
)
);
},
-
- dismissReadPosts() {
- showModal("dismiss-read", { title: "topics.bulk.dismiss_read" });
- },
},
afterRefresh(filter, list, listModel = list) {
@@ -122,32 +136,6 @@ const controllerOpts = {
this.send("loadingComplete");
},
- isFilterPage: function (filter, filterType) {
- if (!filter) {
- return false;
- }
- return filter.match(new RegExp(filterType + "$", "gi")) ? true : false;
- },
-
- @discourseComputed("model.filter", "model.topics.length")
- showDismissRead(filter, topicsLength) {
- return this.isFilterPage(filter, "unread") && topicsLength > 0;
- },
-
- @discourseComputed("model.filter", "model.topics.length")
- showResetNew(filter, topicsLength) {
- return this.isFilterPage(filter, "new") && topicsLength > 0;
- },
-
- @discourseComputed("model.filter", "model.topics.length")
- showDismissAtTop(filter, topicsLength) {
- return (
- (this.isFilterPage(filter, "new") ||
- this.isFilterPage(filter, "unread")) &&
- topicsLength >= 15
- );
- },
-
hasTopics: gt("model.topics.length", 0),
allLoaded: empty("model.more_topics_url"),
latest: endWith("model.filter", "latest"),
diff --git a/app/assets/javascripts/discourse/app/controllers/tag-show.js b/app/assets/javascripts/discourse/app/controllers/tag-show.js
index 0bf4cec6a96..76b7d070cb5 100644
--- a/app/assets/javascripts/discourse/app/controllers/tag-show.js
+++ b/app/assets/javascripts/discourse/app/controllers/tag-show.js
@@ -8,7 +8,6 @@ import Topic from "discourse/models/topic";
import { alias } from "@ember/object/computed";
import bootbox from "bootbox";
import { queryParams } from "discourse/controllers/discovery-sortable";
-import showModal from "discourse/lib/show-modal";
export default Controller.extend(BulkTopicSelection, FilterModeMixin, {
application: controller(),
@@ -93,48 +92,31 @@ export default Controller.extend(BulkTopicSelection, FilterModeMixin, {
}
},
- isFilterPage: function (filter, filterType) {
- if (!filter) {
- return false;
- }
- return filter.match(new RegExp(filterType + "$", "gi")) ? true : false;
- },
-
@discourseComputed("list.filter", "list.topics.length")
showDismissRead(filter, topicsLength) {
- return this.isFilterPage(filter, "unread") && topicsLength > 0;
+ return this._isFilterPage(filter, "unread") && topicsLength > 0;
},
@discourseComputed("list.filter", "list.topics.length")
showResetNew(filter, topicsLength) {
- return this.isFilterPage(filter, "new") && topicsLength > 0;
- },
-
- @discourseComputed("list.filter", "list.topics.length")
- showDismissAtTop(filter, topicsLength) {
- return (
- (this.isFilterPage(filter, "new") ||
- this.isFilterPage(filter, "unread")) &&
- topicsLength >= 15
- );
+ return this._isFilterPage(filter, "new") && topicsLength > 0;
},
actions: {
- dismissReadPosts() {
- showModal("dismiss-read", { title: "topics.bulk.dismiss_read" });
- },
-
resetNew() {
const tracked =
(this.router.currentRoute.queryParams["f"] ||
this.router.currentRoute.queryParams["filter"]) === "tracked";
- Topic.resetNew(
- this.category,
- !this.noSubcategories,
+ let topicIds = this.selected
+ ? this.selected.map((topic) => topic.id)
+ : null;
+
+ Topic.resetNew(this.category, !this.noSubcategories, {
tracked,
- this.tag
- ).then(() =>
+ tag: this.tag,
+ topicIds,
+ }).then(() =>
this.send(
"refresh",
tracked ? { skipResettingParams: ["filter", "f"] } : {}
diff --git a/app/assets/javascripts/discourse/app/controllers/topic.js b/app/assets/javascripts/discourse/app/controllers/topic.js
index 6699af2c537..c6a5289b2b6 100644
--- a/app/assets/javascripts/discourse/app/controllers/topic.js
+++ b/app/assets/javascripts/discourse/app/controllers/topic.js
@@ -1639,7 +1639,7 @@ export default Controller.extend(bufferedProperty("model"), {
function () {
const $post = $(`.topic-post article#post_${postNumber}`);
- if ($post.length === 0 || isElementInViewport($post)) {
+ if ($post.length === 0 || isElementInViewport($post[0])) {
return;
}
diff --git a/app/assets/javascripts/discourse/app/lib/is-element-in-viewport.js b/app/assets/javascripts/discourse/app/lib/is-element-in-viewport.js
index 5bac8fafc83..21d725fd32b 100644
--- a/app/assets/javascripts/discourse/app/lib/is-element-in-viewport.js
+++ b/app/assets/javascripts/discourse/app/lib/is-element-in-viewport.js
@@ -1,6 +1,6 @@
export default function (element) {
- if (element instanceof jQuery) {
- element = element[0];
+ if (!element) {
+ return;
}
const $window = $(window),
diff --git a/app/assets/javascripts/discourse/app/lib/show-modal.js b/app/assets/javascripts/discourse/app/lib/show-modal.js
index f2602e01969..db3067fbcf5 100644
--- a/app/assets/javascripts/discourse/app/lib/show-modal.js
+++ b/app/assets/javascripts/discourse/app/lib/show-modal.js
@@ -41,6 +41,8 @@ export default function (name, opts) {
route.render(fullName, renderArgs);
if (opts.title) {
modalController.set("title", I18n.t(opts.title));
+ } else if (opts.titleTranslated) {
+ modalController.set("title", opts.titleTranslated);
} else {
modalController.set("title", null);
}
diff --git a/app/assets/javascripts/discourse/app/mixins/bulk-topic-selection.js b/app/assets/javascripts/discourse/app/mixins/bulk-topic-selection.js
index 7ba734c65da..f2a11771ee2 100644
--- a/app/assets/javascripts/discourse/app/mixins/bulk-topic-selection.js
+++ b/app/assets/javascripts/discourse/app/mixins/bulk-topic-selection.js
@@ -1,8 +1,8 @@
import Mixin from "@ember/object/mixin";
+import { or } from "@ember/object/computed";
+import { on } from "discourse-common/utils/decorators";
import { NotificationLevels } from "discourse/lib/notification-levels";
import Topic from "discourse/models/topic";
-import { alias } from "@ember/object/computed";
-import { on } from "discourse-common/utils/decorators";
import { inject as service } from "@ember/service";
export default Mixin.create({
@@ -12,13 +12,20 @@ export default Mixin.create({
autoAddTopicsToBulkSelect: false,
selected: null,
- canBulkSelect: alias("currentUser.staff"),
+ canBulkSelect: or("currentUser.staff", "showDismissRead", "showResetNew"),
@on("init")
resetSelected() {
this.set("selected", []);
},
+ _isFilterPage(filter, filterType) {
+ if (!filter) {
+ return false;
+ }
+ return new RegExp(filterType + "$", "gi").test(filter);
+ },
+
actions: {
toggleBulkSelect() {
this.toggleProperty("bulkSelectEnabled");
diff --git a/app/assets/javascripts/discourse/app/models/topic.js b/app/assets/javascripts/discourse/app/models/topic.js
index 37be80796ce..822caf52d98 100644
--- a/app/assets/javascripts/discourse/app/models/topic.js
+++ b/app/assets/javascripts/discourse/app/models/topic.js
@@ -756,7 +756,14 @@ Topic.reopenClass({
});
},
- resetNew(category, include_subcategories, tracked = false, tag = false) {
+ resetNew(category, include_subcategories, opts = {}) {
+ let { tracked, tag, topicIds } = {
+ tracked: false,
+ tag: null,
+ topicIds: null,
+ ...opts,
+ };
+
const data = { tracked };
if (category) {
data.category_id = category.id;
@@ -765,6 +772,9 @@ Topic.reopenClass({
if (tag) {
data.tag_id = tag.id;
}
+ if (topicIds) {
+ data.topic_ids = topicIds;
+ }
return ajax("/topics/reset-new", { type: "PUT", data });
},
diff --git a/app/assets/javascripts/discourse/app/templates/components/bulk-select-button.hbs b/app/assets/javascripts/discourse/app/templates/components/bulk-select-button.hbs
index bdc803ae479..77306f93e11 100644
--- a/app/assets/javascripts/discourse/app/templates/components/bulk-select-button.hbs
+++ b/app/assets/javascripts/discourse/app/templates/components/bulk-select-button.hbs
@@ -1,5 +1,7 @@
-{{#if selected}}
-
- {{d-button class="btn-default bulk-select-btn" action=(action "showBulkActions") icon="wrench"}}
-
+{{#if canDoBulkActions}}
+ {{#if selected}}
+
+ {{d-button class="btn-default bulk-select-btn" action=(action "showBulkActions") icon="wrench"}}
+
+ {{/if}}
{{/if}}
diff --git a/app/assets/javascripts/discourse/app/templates/components/topic-dismiss-buttons.hbs b/app/assets/javascripts/discourse/app/templates/components/topic-dismiss-buttons.hbs
new file mode 100644
index 00000000000..32d9e25d5d4
--- /dev/null
+++ b/app/assets/javascripts/discourse/app/templates/components/topic-dismiss-buttons.hbs
@@ -0,0 +1,21 @@
+{{#if showBasedOnPosition}}
+
+ {{#if showDismissRead}}
+ {{d-button
+ class="btn-default dismiss-read"
+ id=dismissReadId
+ action=(action "dismissReadPosts")
+ translatedLabel=dismissLabel
+ title="topics.bulk.dismiss_tooltip"}}
+ {{/if}}
+ {{#if showResetNew}}
+ {{d-button
+ class="btn-default dismiss-read"
+ id=dismissNewId
+ action=resetNew
+ icon="check"
+ translatedLabel=dismissNewLabel}}
+ {{/if}}
+
+{{/if}}
+
diff --git a/app/assets/javascripts/discourse/app/templates/discovery/topics.hbs b/app/assets/javascripts/discourse/app/templates/discovery/topics.hbs
index ca4e8cbe82c..3e9e8d38757 100644
--- a/app/assets/javascripts/discourse/app/templates/discovery/topics.hbs
+++ b/app/assets/javascripts/discourse/app/templates/discovery/topics.hbs
@@ -2,26 +2,8 @@
{{redirectedReason}}
{{/if}}
-{{#if showDismissAtTop}}
-
- {{#if showDismissRead}}
- {{d-button
- class="btn-default dismiss-read"
- id="dismiss-topics-top"
- action=(action "dismissReadPosts")
- title="topics.bulk.dismiss_tooltip"
- label="topics.bulk.dismiss_button"}}
- {{/if}}
- {{#if showResetNew}}
- {{d-button
- class="btn-default dismiss-read"
- id="dismiss-new-top"
- action=(action "resetNew")
- icon="check"
- label="topics.bulk.dismiss_new"}}
- {{/if}}
-
-{{/if}}
+{{topic-dismiss-buttons position="top" selectedTopics=selected
+model=model showResetNew=showResetNew showDismissRead=showDismissRead resetNew=(action "resetNew")}}
{{#if model.sharedDrafts}}
{{topic-list
@@ -89,22 +71,8 @@