diff --git a/app/assets/javascripts/discourse/app/components/edit-category-tab.js b/app/assets/javascripts/discourse/app/components/edit-category-tab.js index 15875619103..eb0f52c36af 100644 --- a/app/assets/javascripts/discourse/app/components/edit-category-tab.js +++ b/app/assets/javascripts/discourse/app/components/edit-category-tab.js @@ -3,10 +3,14 @@ import discourseComputed from "discourse-common/utils/decorators"; import { scheduleOnce } from "@ember/runloop"; import Component from "@ember/component"; import { propertyEqual } from "discourse/lib/computed"; +import getURL from "discourse-common/lib/get-url"; +import { empty } from "@ember/object/computed"; +import DiscourseURL from "discourse/lib/url"; export default Component.extend({ tagName: "li", classNameBindings: ["active", "tabClassName"], + newCategory: empty("params.slug"), @discourseComputed("tab") tabClassName(tab) { @@ -25,23 +29,32 @@ export default Component.extend({ scheduleOnce("afterRender", this, this._addToCollection); }, + willDestroyElement() { + this._super(...arguments); + + this.setProperties({ + selectedTab: "general", + params: {}, + }); + }, + _addToCollection: function () { this.panels.addObject(this.tabClassName); }, - _resetModalScrollState() { - const $modalBody = $(this.element) - .parents("#discourse-modal") - .find(".modal-body"); - if ($modalBody.length === 1) { - $modalBody.scrollTop(0); - } + @discourseComputed("params.slug", "params.parentSlug") + fullSlug(slug, parentSlug) { + const slugPart = parentSlug && slug ? `${parentSlug}/${slug}` : slug; + return getURL(`/c/${slugPart}/edit/${this.tab}`); }, actions: { select: function () { this.set("selectedTab", this.tab); - this._resetModalScrollState(); + + if (!this.newCategory) { + DiscourseURL.routeTo(this.fullSlug); + } }, }, }); diff --git a/app/assets/javascripts/discourse/app/controllers/edit-category.js b/app/assets/javascripts/discourse/app/controllers/edit-category-tabs.js similarity index 99% rename from app/assets/javascripts/discourse/app/controllers/edit-category.js rename to app/assets/javascripts/discourse/app/controllers/edit-category-tabs.js index 8970521b402..598f0175c9b 100644 --- a/app/assets/javascripts/discourse/app/controllers/edit-category.js +++ b/app/assets/javascripts/discourse/app/controllers/edit-category-tabs.js @@ -15,6 +15,7 @@ export default Controller.extend({ createdCategory: false, expandedMenu: false, mobileView: readOnly("site.mobileView"), + parentParams: null, @on("init") _initPanels() { diff --git a/app/assets/javascripts/discourse/app/models/category.js b/app/assets/javascripts/discourse/app/models/category.js index 5d1c8ce2f2f..f504c9f3688 100644 --- a/app/assets/javascripts/discourse/app/models/category.js +++ b/app/assets/javascripts/discourse/app/models/category.js @@ -485,6 +485,25 @@ Category.reopenClass({ return ajax(`/c/${slugPath}/find_by_slug.json`); }, + reloadCategoryWithPermissions(params, store, site) { + if (params.slug && params.slug.match(/^\d+-category/)) { + const id = parseInt(params.slug, 10); + return this.reloadById(id).then((result) => + this._includePermissions(result.category, store, site) + ); + } + return this.reloadBySlug(params.slug, params.parentSlug).then((result) => + this._includePermissions(result.category, store, site) + ); + }, + + _includePermissions(category, store, site) { + const record = store.createRecord("category", category); + record.setupGroupsAndPermissions(); + site.updateCategory(record); + return record; + }, + search(term, opts) { var limit = 5; diff --git a/app/assets/javascripts/discourse/app/routes/app-route-map.js b/app/assets/javascripts/discourse/app/routes/app-route-map.js index 81b8dec96ba..c3c5f17193f 100644 --- a/app/assets/javascripts/discourse/app/routes/app-route-map.js +++ b/app/assets/javascripts/discourse/app/routes/app-route-map.js @@ -22,6 +22,16 @@ export default function () { this.route("topicBySlugOrId", { path: "/t/:slugOrId", resetNamespace: true }); this.route("newCategory", { path: "/new-category" }); + this.route("editCategory", { path: "/c/:slug/edit" }, function () { + this.route("tabs", { path: "/:tab" }); + }); + this.route( + "editChildCategory", + { path: "/c/:parentSlug/:slug/edit" }, + function () { + this.route("tabs", { path: "/:tab" }); + } + ); this.route("discovery", { path: "/", resetNamespace: true }, function () { // legacy route @@ -65,8 +75,6 @@ export default function () { }); this.route("categories"); - this.route("editCategory", { path: "/c/:slug/edit" }); - this.route("editChildCategory", { path: "/c/:parentSlug/:slug/edit" }); // legacy routes this.route("parentCategory", { path: "/c/:slug" }); diff --git a/app/assets/javascripts/discourse/app/routes/discovery-edit-category.js b/app/assets/javascripts/discourse/app/routes/discovery-edit-category.js deleted file mode 100644 index a30a22713ac..00000000000 --- a/app/assets/javascripts/discourse/app/routes/discovery-edit-category.js +++ /dev/null @@ -1,28 +0,0 @@ -import I18n from "I18n"; -import DiscourseRoute from "discourse/routes/discourse"; -import Category from "discourse/models/category"; - -export default DiscourseRoute.extend({ - model(params) { - return Category.reloadBySlugPath(params.slug).then((result) => { - const record = this.store.createRecord("category", result.category); - record.setupGroupsAndPermissions(); - this.site.updateCategory(record); - return record; - }); - }, - - titleToken() { - return I18n.t("category.edit_dialog_title", { - categoryName: this.currentModel.name, - }); - }, - - renderTemplate() { - this.render("edit-category", { - controller: "edit-category", - outlet: "list-container", - model: this.currentModel, - }); - }, -}); diff --git a/app/assets/javascripts/discourse/app/routes/discovery-edit-child-category.js b/app/assets/javascripts/discourse/app/routes/discovery-edit-child-category.js deleted file mode 100644 index ae147729f5f..00000000000 --- a/app/assets/javascripts/discourse/app/routes/discovery-edit-child-category.js +++ /dev/null @@ -1,30 +0,0 @@ -import I18n from "I18n"; -import DiscourseRoute from "discourse/routes/discourse"; -import Category from "discourse/models/category"; - -export default DiscourseRoute.extend({ - model(params) { - return Category.reloadBySlug(params.slug, params.parentSlug).then( - (result) => { - const record = this.store.createRecord("category", result.category); - record.setupGroupsAndPermissions(); - this.site.updateCategory(record); - return record; - } - ); - }, - - titleToken() { - return I18n.t("category.edit_dialog_title", { - categoryName: this.currentModel.name, - }); - }, - - renderTemplate() { - this.render("edit-category", { - controller: "edit-category", - outlet: "list-container", - model: this.currentModel, - }); - }, -}); diff --git a/app/assets/javascripts/discourse/app/routes/edit-category-index.js b/app/assets/javascripts/discourse/app/routes/edit-category-index.js new file mode 100644 index 00000000000..17d20e52dfd --- /dev/null +++ b/app/assets/javascripts/discourse/app/routes/edit-category-index.js @@ -0,0 +1,8 @@ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ + afterModel() { + const params = this.paramsFor("editCategory"); + this.replaceWith(`/c/${params.slug}/edit/general`); + }, +}); diff --git a/app/assets/javascripts/discourse/app/routes/edit-category-tabs.js b/app/assets/javascripts/discourse/app/routes/edit-category-tabs.js new file mode 100644 index 00000000000..07f8c7243d0 --- /dev/null +++ b/app/assets/javascripts/discourse/app/routes/edit-category-tabs.js @@ -0,0 +1,18 @@ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ + model() { + return this.modelFor("editCategory"); + }, + + setupController(controller, model, transition) { + this._super(...arguments); + + const parentParams = this.paramsFor("editCategory"); + + controller.setProperties({ + parentParams, + selectedTab: transition.to.params.tab, + }); + }, +}); diff --git a/app/assets/javascripts/discourse/app/routes/edit-category.js b/app/assets/javascripts/discourse/app/routes/edit-category.js new file mode 100644 index 00000000000..ae8136c4a0e --- /dev/null +++ b/app/assets/javascripts/discourse/app/routes/edit-category.js @@ -0,0 +1,19 @@ +import I18n from "I18n"; +import DiscourseRoute from "discourse/routes/discourse"; +import Category from "discourse/models/category"; + +export default DiscourseRoute.extend({ + model(params) { + return Category.reloadCategoryWithPermissions( + params, + this.store, + this.site + ); + }, + + titleToken() { + return I18n.t("category.edit_dialog_title", { + categoryName: this.currentModel.name, + }); + }, +}); diff --git a/app/assets/javascripts/discourse/app/routes/edit-child-category-index.js b/app/assets/javascripts/discourse/app/routes/edit-child-category-index.js new file mode 100644 index 00000000000..43c73864de4 --- /dev/null +++ b/app/assets/javascripts/discourse/app/routes/edit-child-category-index.js @@ -0,0 +1,8 @@ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ + afterModel() { + const params = this.paramsFor("editChildCategory"); + this.replaceWith(`/c/${params.parentSlug}/${params.slug}/edit/general`); + }, +}); diff --git a/app/assets/javascripts/discourse/app/routes/edit-child-category-tabs.js b/app/assets/javascripts/discourse/app/routes/edit-child-category-tabs.js new file mode 100644 index 00000000000..402bed4910e --- /dev/null +++ b/app/assets/javascripts/discourse/app/routes/edit-child-category-tabs.js @@ -0,0 +1,19 @@ +import DiscourseRoute from "discourse/routes/discourse"; + +export default DiscourseRoute.extend({ + model() { + return this.modelFor("editChildCategory"); + }, + + renderTemplate() { + this.render("edit-category-tabs", { + controller: "edit-category-tabs", + model: this.currentModel, + }); + + this.controllerFor("editCategory.tabs").set( + "parentParams", + this.paramsFor("editChildCategory") + ); + }, +}); diff --git a/app/assets/javascripts/discourse/app/routes/edit-child-category.js b/app/assets/javascripts/discourse/app/routes/edit-child-category.js new file mode 100644 index 00000000000..ae8136c4a0e --- /dev/null +++ b/app/assets/javascripts/discourse/app/routes/edit-child-category.js @@ -0,0 +1,19 @@ +import I18n from "I18n"; +import DiscourseRoute from "discourse/routes/discourse"; +import Category from "discourse/models/category"; + +export default DiscourseRoute.extend({ + model(params) { + return Category.reloadCategoryWithPermissions( + params, + this.store, + this.site + ); + }, + + titleToken() { + return I18n.t("category.edit_dialog_title", { + categoryName: this.currentModel.name, + }); + }, +}); diff --git a/app/assets/javascripts/discourse/app/routes/new-category.js b/app/assets/javascripts/discourse/app/routes/new-category.js index e4aad400ed3..20ab5603657 100644 --- a/app/assets/javascripts/discourse/app/routes/new-category.js +++ b/app/assets/javascripts/discourse/app/routes/new-category.js @@ -24,8 +24,8 @@ export default DiscourseRoute.extend({ }, renderTemplate() { - this.render("edit-category", { - controller: "edit-category", + this.render("edit-category-tabs", { + controller: "edit-category-tabs", model: this.currentModel, }); }, diff --git a/app/assets/javascripts/discourse/app/templates/edit-category.hbs b/app/assets/javascripts/discourse/app/templates/edit-category-tabs.hbs similarity index 74% rename from app/assets/javascripts/discourse/app/templates/edit-category.hbs rename to app/assets/javascripts/discourse/app/templates/edit-category-tabs.hbs index ff0ed489e31..8615c267a1a 100644 --- a/app/assets/javascripts/discourse/app/templates/edit-category.hbs +++ b/app/assets/javascripts/discourse/app/templates/edit-category-tabs.hbs @@ -1,12 +1,12 @@
diff --git a/app/assets/javascripts/discourse/tests/acceptance/category-edit-security-test.js b/app/assets/javascripts/discourse/tests/acceptance/category-edit-security-test.js index 2d30cd386d3..ebb580fc6b0 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/category-edit-security-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/category-edit-security-test.js @@ -7,10 +7,7 @@ acceptance("Category Edit - security", function (needs) { needs.user(); test("default", async (assert) => { - await visit("/c/bug"); - - await click(".edit-category"); - await click("li.edit-category-security a"); + await visit("/c/bug/edit/security"); const $permissionListItems = find(".permission-list li"); @@ -24,10 +21,8 @@ acceptance("Category Edit - security", function (needs) { test("removing a permission", async (assert) => { const availableGroups = selectKit(".available-groups"); - await visit("/c/bug"); + await visit("/c/bug/edit/security"); - await click(".edit-category"); - await click("li.edit-category-security a"); await click(".edit-category-tab-security .edit-permission"); await availableGroups.expand(); @@ -51,10 +46,8 @@ acceptance("Category Edit - security", function (needs) { const availableGroups = selectKit(".available-groups"); const permissionSelector = selectKit(".permission-selector"); - await visit("/c/bug"); + await visit("/c/bug/edit/security"); - await click(".edit-category"); - await click("li.edit-category-security a"); await click(".edit-category-tab-security .edit-permission"); await availableGroups.expand(); await availableGroups.selectRowByValue("staff"); @@ -76,10 +69,8 @@ acceptance("Category Edit - security", function (needs) { test("adding a previously removed permission", async (assert) => { const availableGroups = selectKit(".available-groups"); - await visit("/c/bug"); + await visit("/c/bug/edit/security"); - await click(".edit-category"); - await click("li.edit-category-security a"); await click(".edit-category-tab-security .edit-permission"); await click( ".edit-category-tab-security .permission-list li:first-of-type .remove-permission" diff --git a/app/assets/javascripts/discourse/tests/acceptance/category-edit-test.js b/app/assets/javascripts/discourse/tests/acceptance/category-edit-test.js index cc76a102e1a..7c14b635081 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/category-edit-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/category-edit-test.js @@ -2,6 +2,8 @@ import { click, fillIn, visit, currentURL } from "@ember/test-helpers"; import { test } from "qunit"; import selectKit from "discourse/tests/helpers/select-kit-helper"; import { acceptance } from "discourse/tests/helpers/qunit-helpers"; +import DiscourseURL from "discourse/lib/url"; +import sinon from "sinon"; acceptance("Category Edit", function (needs) { needs.user(); @@ -11,7 +13,11 @@ acceptance("Category Edit", function (needs) { await visit("/c/bug"); await click("button.edit-category"); - assert.equal(currentURL(), "/c/bug/edit", "it jumps to the correct screen"); + assert.equal( + currentURL(), + "/c/bug/edit/general", + "it jumps to the correct screen" + ); assert.equal(find(".badge-category").text(), "bug"); await fillIn("input.category-name", "testing"); @@ -22,19 +28,55 @@ acceptance("Category Edit", function (needs) { await click(".edit-category-topic-template"); await fillIn(".d-editor-input", "this is the new topic template"); - await click(".edit-category-settings"); + await click("#save-category"); + assert.equal( + currentURL(), + "/c/bug/edit/general", + "it stays on the edit screen" + ); + + await visit("/c/bug/edit/settings"); const searchPriorityChooser = selectKit("#category-search-priority"); await searchPriorityChooser.expand(); await searchPriorityChooser.selectRowByValue(1); await click("#save-category"); - assert.equal(currentURL(), "/c/bug/edit", "it stays on the edit screen"); + assert.equal( + currentURL(), + "/c/bug/edit/settings", + "it stays on the edit screen" + ); + + sinon.stub(DiscourseURL, "routeTo"); + + await click(".edit-category-security a"); + assert.ok( + DiscourseURL.routeTo.calledWith("/c/bug/edit/security"), + "tab routing works" + ); + }); + + test("Index Route", async (assert) => { + await visit("/c/bug/edit"); + assert.equal( + currentURL(), + "/c/bug/edit/general", + "it redirects to the general tab" + ); + }); + + test("Slugless Route", async (assert) => { + await visit("/c/1-category/edit"); + assert.equal( + currentURL(), + "/c/1-category/edit/general", + "it goes to the general tab" + ); + assert.equal(find("input.category-name").val(), "bug"); }); test("Error Saving", async (assert) => { - await visit("/c/bug"); - await click("button.edit-category"); - await click(".edit-category-settings"); + await visit("/c/bug/edit/settings"); await fillIn(".email-in", "duplicate@example.com"); await click("#save-category"); @@ -46,13 +88,7 @@ acceptance("Category Edit", function (needs) { }); test("Subcategory list settings", async (assert) => { - const categoryChooser = selectKit( - ".edit-category-tab-general .category-chooser" - ); - - await visit("/c/bug"); - await click("button.edit-category"); - await click(".edit-category-settings a"); + await visit("/c/bug/edit/settings"); assert.ok( !visible(".subcategory-list-style-field"), @@ -66,11 +102,15 @@ acceptance("Category Edit", function (needs) { "subcategory list style is shown if show subcategory list is checked" ); - await click(".edit-category-general"); + await visit("/c/bug/edit/general"); + + const categoryChooser = selectKit( + ".edit-category-tab-general .category-chooser" + ); await categoryChooser.expand(); await categoryChooser.selectRowByValue(3); - await click(".edit-category-settings a"); + await visit("/c/bug/edit/settings"); assert.ok( !visible(".show-subcategory-list-field"), diff --git a/app/assets/javascripts/discourse/tests/acceptance/category-new-test.js b/app/assets/javascripts/discourse/tests/acceptance/category-new-test.js index f5726fb5c9d..22bd1c74045 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/category-new-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/category-new-test.js @@ -3,6 +3,7 @@ import { test } from "qunit"; import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import I18n from "I18n"; import DiscourseURL from "discourse/lib/url"; +import sinon from "sinon"; acceptance("Category New", function (needs) { needs.user(); @@ -23,11 +24,23 @@ acceptance("Category New", function (needs) { }) ); - await click(".category-back"); + await click(".edit-category-security a"); + assert.ok( + find("button.edit-permission"), + "it can switch to the security tab" + ); - assert.equal( - DiscourseURL.redirectedTo, - "/c/testing/11", + await click(".edit-category-settings a"); + assert.ok( + find("#category-search-priority"), + "it can switch to the settings tab" + ); + + sinon.stub(DiscourseURL, "redirectTo"); + + await click(".category-back"); + assert.ok( + DiscourseURL.redirectTo.calledWith("/c/testing/11"), "it full page redirects after a newly created category" ); }); diff --git a/app/assets/javascripts/discourse/tests/acceptance/password-reset-test.js b/app/assets/javascripts/discourse/tests/acceptance/password-reset-test.js index e27859df8ba..44c19384123 100644 --- a/app/assets/javascripts/discourse/tests/acceptance/password-reset-test.js +++ b/app/assets/javascripts/discourse/tests/acceptance/password-reset-test.js @@ -4,6 +4,8 @@ import I18n from "I18n"; import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import PreloadStore from "discourse/lib/preload-store"; import { parsePostData } from "discourse/tests/helpers/create-pretender"; +import DiscourseURL from "discourse/lib/url"; +import sinon from "sinon"; acceptance("Password Reset", function (needs) { needs.pretender((server, helper) => { @@ -84,8 +86,9 @@ acceptance("Password Reset", function (needs) { ); await fillIn(".password-reset input", "perf3ctly5ecur3"); + sinon.stub(DiscourseURL, "redirectTo"); await click(".password-reset form button"); - assert.ok(!exists(".password-reset form"), "form is gone"); + assert.ok(DiscourseURL.redirectTo.calledWith("/"), "form is gone"); }); test("Password Reset Page With Second Factor", async (assert) => { @@ -116,8 +119,12 @@ acceptance("Password Reset", function (needs) { assert.ok(exists("#new-account-password"), "shows the input"); await fillIn(".password-reset input", "perf3ctly5ecur3"); - await click(".password-reset form button"); - assert.ok(!exists(".password-reset form"), "form is gone"); + sinon.stub(DiscourseURL, "redirectTo"); + await click(".password-reset form button"); + assert.ok( + DiscourseURL.redirectTo.calledWith("/"), + "it redirects after submitting form" + ); }); }); diff --git a/app/assets/javascripts/discourse/tests/setup-tests.js b/app/assets/javascripts/discourse/tests/setup-tests.js index b43fae6a8b9..abd60524ae8 100644 --- a/app/assets/javascripts/discourse/tests/setup-tests.js +++ b/app/assets/javascripts/discourse/tests/setup-tests.js @@ -12,7 +12,6 @@ import createPretender, { } from "discourse/tests/helpers/create-pretender"; import { flushMap } from "discourse/models/store"; import { ScrollingDOMMethods } from "discourse/mixins/scrolling"; -import DiscourseURL from "discourse/lib/url"; import { resetSite, applyPretender, @@ -130,11 +129,6 @@ export default function setupTests(app, container) { site, }); - DiscourseURL.redirectedTo = null; - DiscourseURL.redirectTo = function (url) { - DiscourseURL.redirectedTo = url; - }; - PreloadStore.reset(); window.sinon.stub(ScrollingDOMMethods, "screenNotFull"); diff --git a/config/routes.rb b/config/routes.rb index 1443136eddf..791621e5f4e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -682,8 +682,8 @@ Discourse::Application.routes.draw do get "c/:category_slug/find_by_slug" => "categories#find_by_slug" get "c/:parent_category_slug/:category_slug/find_by_slug" => "categories#find_by_slug" - get "c/:category_slug/edit" => "categories#find_by_slug", constraints: { format: 'html' } - get "c/:parent_category_slug/:category_slug/edit" => "categories#find_by_slug", constraints: { format: 'html' } + get "c/:category_slug/edit(/:tab)" => "categories#find_by_slug", constraints: { format: 'html' } + get "c/:parent_category_slug/:category_slug/edit(/:tab)" => "categories#find_by_slug", constraints: { format: 'html' } get "/new-category" => "categories#show", constraints: { format: 'html' } get "c/*category_slug_path_with_id.rss" => "list#category_feed", format: :rss