diff --git a/app/assets/javascripts/discourse/app/components/discovery/navigation.js b/app/assets/javascripts/discourse/app/components/discovery/navigation.js index bde1de83439..f6762f8d75f 100644 --- a/app/assets/javascripts/discourse/app/components/discovery/navigation.js +++ b/app/assets/javascripts/discourse/app/components/discovery/navigation.js @@ -1,8 +1,8 @@ import Component from "@glimmer/component"; import { action } from "@ember/object"; import { inject as service } from "@ember/service"; +import ReorderCategories from "discourse/components/modal/reorder-categories"; import { calculateFilterMode } from "discourse/lib/filter-mode"; -import showModal from "discourse/lib/show-modal"; import { TRACKED_QUERY_PARAM_VALUE } from "discourse/lib/topic-list-tracked-filter"; import DiscourseURL from "discourse/lib/url"; import Category from "discourse/models/category"; @@ -56,6 +56,6 @@ export default class DiscoveryNavigation extends Component { @action reorderCategories() { - showModal("reorder-categories"); + this.modal.show(ReorderCategories); } } diff --git a/app/assets/javascripts/discourse/app/components/modal/reorder-categories.hbs b/app/assets/javascripts/discourse/app/components/modal/reorder-categories.hbs new file mode 100644 index 00000000000..06c04f940bc --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/modal/reorder-categories.hbs @@ -0,0 +1,53 @@ + + <:body> + + + + + + + {{#each this.categoriesOrdered as |category|}} + + + + + + {{/each}} + +
{{i18n "categories.category"}}{{i18n "categories.reorder.position"}}
+
+ {{category-badge category allowUncategorized="true"}} +
+
+
+ + + +
+
+ + + <:footer> + + +
\ No newline at end of file diff --git a/app/assets/javascripts/discourse/app/controllers/reorder-categories.js b/app/assets/javascripts/discourse/app/components/modal/reorder-categories.js similarity index 55% rename from app/assets/javascripts/discourse/app/controllers/reorder-categories.js rename to app/assets/javascripts/discourse/app/components/modal/reorder-categories.js index b339baab102..9a06bb59ff9 100644 --- a/app/assets/javascripts/discourse/app/controllers/reorder-categories.js +++ b/app/assets/javascripts/discourse/app/components/modal/reorder-categories.js @@ -1,24 +1,22 @@ -import Controller from "@ember/controller"; +import Component from "@ember/component"; +import { action } from "@ember/object"; import { sort } from "@ember/object/computed"; -import Evented from "@ember/object/evented"; -import BufferedProxy from "ember-buffered-proxy/proxy"; +import { next } from "@ember/runloop"; +import { inject as service } from "@ember/service"; import { ajax } from "discourse/lib/ajax"; import { popupAjaxError } from "discourse/lib/ajax-error"; -import ModalFunctionality from "discourse/mixins/modal-functionality"; -import discourseComputed, { on } from "discourse-common/utils/decorators"; -export default Controller.extend(ModalFunctionality, Evented, { +export default class ReorderCategories extends Component { + @service site; + + categoriesSorting = ["position"]; + + @sort("site.categories", "categoriesSorting") categoriesOrdered; + init() { - this._super(...arguments); - this.categoriesSorting = ["position"]; - }, - - @discourseComputed("site.categories.[]") - categoriesBuffered(categories) { - return (categories || []).map((c) => BufferedProxy.create({ content: c })); - }, - - categoriesOrdered: sort("categoriesBuffered", "categoriesSorting"), + super.init(...arguments); + next(() => this.reorder()); + } /** * 1. Make sure all categories have unique position numbers. @@ -31,46 +29,39 @@ export default Controller.extend(ModalFunctionality, Evented, { * parent => parent/c2 * other parent/c2/c1 * parent/c2 other - * **/ - @on("init") reorder() { - const reorderChildren = (categoryId, depth, index) => { - this.categoriesOrdered.forEach((category) => { - if ( - (categoryId === null && !category.get("parent_category_id")) || - category.get("parent_category_id") === categoryId - ) { - category.setProperties({ depth, position: index++ }); - index = reorderChildren(category.get("id"), depth + 1, index); - } - }); + this.reorderChildren(null, 0, 0); + } - return index; - }; - - reorderChildren(null, 0, 0); - - this.categoriesBuffered.forEach((bc) => { - if (bc.get("hasBufferedChanges")) { - bc.applyBufferedChanges(); + reorderChildren(categoryId, depth, index) { + for (const category of this.categoriesOrdered) { + if ( + (categoryId === null && !category.get("parent_category_id")) || + category.get("parent_category_id") === categoryId + ) { + category.setProperties({ depth, position: index++ }); + index = this.reorderChildren(category.get("id"), depth + 1, index); } - }); + } - this.notifyPropertyChange("categoriesBuffered"); - }, + return index; + } countDescendants(category) { - return category.get("subcategories") - ? category - .get("subcategories") - .reduce( - (count, subcategory) => count + this.countDescendants(subcategory), - category.get("subcategories").length - ) - : 0; - }, + if (!category.get("subcategories")) { + return 0; + } + return category + .get("subcategories") + .reduce( + (count, subcategory) => count + this.countDescendants(subcategory), + category.get("subcategories").length + ); + } + + @action move(category, direction) { let targetPosition = category.get("position") + direction; @@ -114,7 +105,7 @@ export default Controller.extend(ModalFunctionality, Evented, { } // Update other categories between current and target position - this.categoriesOrdered.map((c) => { + for (const c of this.categoriesOrdered) { if (direction < 0) { // Moving up (position gets smaller) if ( @@ -134,47 +125,42 @@ export default Controller.extend(ModalFunctionality, Evented, { c.set("position", newPosition); } } - }); + } // Update this category's position to target position category.set("position", targetPosition); this.reorder(); - }, + } - actions: { - change(category, event) { - let newPosition = parseFloat(event.target.value); - newPosition = - newPosition < category.get("position") - ? Math.ceil(newPosition) - : Math.floor(newPosition); - const direction = newPosition - category.get("position"); - this.move(category, direction); - }, + @action + change(category, event) { + let newPosition = parseFloat(event.target.value); + newPosition = + newPosition < category.get("position") + ? Math.ceil(newPosition) + : Math.floor(newPosition); + const direction = newPosition - category.get("position"); + this.move(category, direction); + } - moveUp(category) { - this.move(category, -1); - }, + @action + async save() { + this.reorder(); - moveDown(category) { - this.move(category, 1); - }, + const data = {}; + for (const category of this.site.categories) { + data[category.get("id")] = category.get("position"); + } - save() { - this.reorder(); - - const data = {}; - this.categoriesBuffered.forEach((cat) => { - data[cat.get("id")] = cat.get("position"); - }); - - ajax("/categories/reorder", { + try { + await ajax("/categories/reorder", { type: "POST", data: { mapping: JSON.stringify(data) }, - }) - .then(() => window.location.reload()) - .catch(popupAjaxError); - }, - }, -}); + }); + window.location.reload(); + } catch (e) { + popupAjaxError(e); + } + } +} diff --git a/app/assets/javascripts/discourse/app/routes/discovery-categories.js b/app/assets/javascripts/discourse/app/routes/discovery-categories.js index 83dafac22ce..f5f8960f88b 100644 --- a/app/assets/javascripts/discourse/app/routes/discovery-categories.js +++ b/app/assets/javascripts/discourse/app/routes/discovery-categories.js @@ -10,6 +10,7 @@ import DiscourseRoute from "discourse/routes/discourse"; import I18n from "discourse-i18n"; export default class DiscoveryCategoriesRoute extends DiscourseRoute { + @service modal; @service router; @service session; diff --git a/app/assets/javascripts/discourse/app/services/modal.js b/app/assets/javascripts/discourse/app/services/modal.js index bfc5ea96d0e..19c2c7777b6 100644 --- a/app/assets/javascripts/discourse/app/services/modal.js +++ b/app/assets/javascripts/discourse/app/services/modal.js @@ -11,20 +11,6 @@ import deprecated, { } from "discourse-common/lib/deprecated"; import I18n from "discourse-i18n"; -// Known legacy modals in core. Silence deprecation warnings for these so the messages -// don't cause unnecessary noise. -const KNOWN_LEGACY_MODALS = [ - "avatar-selector", - "change-owner", - "change-post-notice", - "create-invite-bulk", - "create-invite", - "grant-badge", - "group-default-notifications", - "reject-reason-reviewable", - "reorder-categories", -]; - const LEGACY_OPTS = new Set([ "admin", "templateName", @@ -139,17 +125,15 @@ export default class ModalServiceWithLegacySupport extends ModalService { this.close({ initiatedBy: CLOSE_INITIATED_BY_MODAL_SHOW }); - if (!KNOWN_LEGACY_MODALS.includes(modal)) { - deprecated( - `Defining modals using a controller is deprecated. Use the component-based API instead. (modal: ${modal})`, - { - id: "discourse.modal-controllers", - since: "3.1", - dropFrom: "3.2", - url: "https://meta.discourse.org/t/268057", - } - ); - } + deprecated( + `Defining modals using a controller is deprecated. Use the component-based API instead. (modal: ${modal})`, + { + id: "discourse.modal-controllers", + since: "3.1", + dropFrom: "3.2", + url: "https://meta.discourse.org/t/268057", + } + ); const name = modal; const container = getOwner(this); diff --git a/app/assets/javascripts/discourse/app/templates/modal/reorder-categories.hbs b/app/assets/javascripts/discourse/app/templates/modal/reorder-categories.hbs deleted file mode 100644 index 58bb885f9c6..00000000000 --- a/app/assets/javascripts/discourse/app/templates/modal/reorder-categories.hbs +++ /dev/null @@ -1,50 +0,0 @@ - -
- - - - - - - {{#each this.categoriesOrdered as |cat|}} - - - - - - {{/each}} - -
{{i18n "categories.category"}}{{i18n "categories.reorder.position"}}
-
- {{category-badge cat allowUncategorized="true"}} -
-
- - - -
-
-
- - \ No newline at end of file diff --git a/app/assets/javascripts/discourse/tests/unit/controllers/reorder-categories-test.js b/app/assets/javascripts/discourse/tests/unit/components/reorder-categories-test.js similarity index 79% rename from app/assets/javascripts/discourse/tests/unit/controllers/reorder-categories-test.js rename to app/assets/javascripts/discourse/tests/unit/components/reorder-categories-test.js index 1456adc9da6..941a44a1b02 100644 --- a/app/assets/javascripts/discourse/tests/unit/controllers/reorder-categories-test.js +++ b/app/assets/javascripts/discourse/tests/unit/components/reorder-categories-test.js @@ -2,11 +2,13 @@ import { getOwner } from "@ember/application"; import { setupTest } from "ember-qunit"; import { module, test } from "qunit"; -module("Unit | Controller | reorder-categories", function (hooks) { +module("Unit | Component | reorder-categories", function (hooks) { setupTest(hooks); test("reorder set unique position number", function (assert) { - const controller = getOwner(this).lookup("controller:reorder-categories"); + const component = this.owner + .factoryFor("component:modal/reorder-categories") + .create(); const store = getOwner(this).lookup("service:store"); const site = getOwner(this).lookup("service:site"); @@ -16,15 +18,17 @@ module("Unit | Controller | reorder-categories", function (hooks) { store.createRecord("category", { id: 3, position: 0 }), ]); - controller.reorder(); + component.reorder(); - controller.categoriesOrdered.forEach((category, index) => { + component.categoriesOrdered.forEach((category, index) => { assert.strictEqual(category.get("position"), index); }); }); test("reorder places subcategories after their parent categories, while maintaining the relative order", function (assert) { - const controller = getOwner(this).lookup("controller:reorder-categories"); + const component = this.owner + .factoryFor("component:modal/reorder-categories") + .create(); const store = getOwner(this).lookup("service:store"); const parent = store.createRecord("category", { @@ -54,16 +58,18 @@ module("Unit | Controller | reorder-categories", function (hooks) { const site = getOwner(this).lookup("service:site"); site.set("categories", [child2, parent, other, child1]); - controller.reorder(); + component.reorder(); assert.deepEqual( - controller.categoriesOrdered.mapBy("slug"), + component.categoriesOrdered.mapBy("slug"), expectedOrderSlugs ); }); test("changing the position number of a category should place it at given position", function (assert) { - const controller = getOwner(this).lookup("controller:reorder-categories"); + const component = this.owner + .factoryFor("component:modal/reorder-categories") + .create(); const store = getOwner(this).lookup("service:store"); const elem1 = store.createRecord("category", { @@ -88,9 +94,9 @@ module("Unit | Controller | reorder-categories", function (hooks) { site.set("categories", [elem1, elem2, elem3]); // Move category 'foo' from position 0 to position 2 - controller.send("change", elem1, { target: { value: "2" } }); + component.change(elem1, { target: { value: "2" } }); - assert.deepEqual(controller.categoriesOrdered.mapBy("slug"), [ + assert.deepEqual(component.categoriesOrdered.mapBy("slug"), [ "bar", "test", "foo", @@ -98,7 +104,9 @@ module("Unit | Controller | reorder-categories", function (hooks) { }); test("changing the position number of a category should place it at given position and respect children", function (assert) { - const controller = getOwner(this).lookup("controller:reorder-categories"); + const component = this.owner + .factoryFor("component:modal/reorder-categories") + .create(); const store = getOwner(this).lookup("service:store"); const elem1 = store.createRecord("category", { @@ -129,9 +137,9 @@ module("Unit | Controller | reorder-categories", function (hooks) { const site = getOwner(this).lookup("service:site"); site.set("categories", [elem1, child1, elem2, elem3]); - controller.send("change", elem1, { target: { value: 3 } }); + component.change(elem1, { target: { value: 3 } }); - assert.deepEqual(controller.categoriesOrdered.mapBy("slug"), [ + assert.deepEqual(component.categoriesOrdered.mapBy("slug"), [ "bar", "test", "foo", @@ -140,7 +148,9 @@ module("Unit | Controller | reorder-categories", function (hooks) { }); test("changing the position through click on arrow of a category should place it at given position and respect children", function (assert) { - const controller = getOwner(this).lookup("controller:reorder-categories"); + const component = this.owner + .factoryFor("component:modal/reorder-categories") + .create(); const store = getOwner(this).lookup("service:store"); const child2 = store.createRecord("category", { @@ -180,11 +190,11 @@ module("Unit | Controller | reorder-categories", function (hooks) { const site = getOwner(this).lookup("service:site"); site.set("categories", [elem1, child1, child2, elem2, elem3]); - controller.reorder(); + component.reorder(); - controller.send("moveDown", elem1); + component.move(elem1, 1); - assert.deepEqual(controller.categoriesOrdered.mapBy("slug"), [ + assert.deepEqual(component.categoriesOrdered.mapBy("slug"), [ "bar", "foo", "foo-child", diff --git a/app/assets/stylesheets/common/base/_index.scss b/app/assets/stylesheets/common/base/_index.scss index a77ffbd4ce9..69bf3ffd132 100644 --- a/app/assets/stylesheets/common/base/_index.scss +++ b/app/assets/stylesheets/common/base/_index.scss @@ -3,7 +3,7 @@ @import "activation"; @import "alert"; @import "bbcode"; -@import "cat_reorder"; +@import "reorder-categories"; @import "category-list"; @import "code_highlighting"; @import "colorpicker"; diff --git a/app/assets/stylesheets/common/base/cat_reorder.scss b/app/assets/stylesheets/common/base/reorder-categories.scss similarity index 89% rename from app/assets/stylesheets/common/base/cat_reorder.scss rename to app/assets/stylesheets/common/base/reorder-categories.scss index ac50234a654..e325842aa96 100644 --- a/app/assets/stylesheets/common/base/cat_reorder.scss +++ b/app/assets/stylesheets/common/base/reorder-categories.scss @@ -5,16 +5,15 @@ } } input[type="text"] { + margin: 0; max-width: 2.5em; padding: 0.35em; text-align: center; + @include breakpoint(mobile-extra-large) { width: 2em; } } - #rc-scroll-anchor { - padding: 0; - } table { padding-bottom: 150px; margin: 0 0.667em; @@ -34,6 +33,11 @@ } } +.reorder-categories-actions { + display: flex; + gap: 0.5rem; +} + .reorder-categories-depth-1 { margin-left: 20px; }