Add routing for category edit screens (#11027)

Also fixes category editing for instances with slug generation set to "none".
This commit is contained in:
Penar Musaraj 2020-10-28 09:59:38 -04:00 committed by GitHub
parent 6d4cfbf120
commit ab6894ea36
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 238 additions and 119 deletions

View File

@ -3,10 +3,14 @@ import discourseComputed from "discourse-common/utils/decorators";
import { scheduleOnce } from "@ember/runloop"; import { scheduleOnce } from "@ember/runloop";
import Component from "@ember/component"; import Component from "@ember/component";
import { propertyEqual } from "discourse/lib/computed"; 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({ export default Component.extend({
tagName: "li", tagName: "li",
classNameBindings: ["active", "tabClassName"], classNameBindings: ["active", "tabClassName"],
newCategory: empty("params.slug"),
@discourseComputed("tab") @discourseComputed("tab")
tabClassName(tab) { tabClassName(tab) {
@ -25,23 +29,32 @@ export default Component.extend({
scheduleOnce("afterRender", this, this._addToCollection); scheduleOnce("afterRender", this, this._addToCollection);
}, },
willDestroyElement() {
this._super(...arguments);
this.setProperties({
selectedTab: "general",
params: {},
});
},
_addToCollection: function () { _addToCollection: function () {
this.panels.addObject(this.tabClassName); this.panels.addObject(this.tabClassName);
}, },
_resetModalScrollState() { @discourseComputed("params.slug", "params.parentSlug")
const $modalBody = $(this.element) fullSlug(slug, parentSlug) {
.parents("#discourse-modal") const slugPart = parentSlug && slug ? `${parentSlug}/${slug}` : slug;
.find(".modal-body"); return getURL(`/c/${slugPart}/edit/${this.tab}`);
if ($modalBody.length === 1) {
$modalBody.scrollTop(0);
}
}, },
actions: { actions: {
select: function () { select: function () {
this.set("selectedTab", this.tab); this.set("selectedTab", this.tab);
this._resetModalScrollState();
if (!this.newCategory) {
DiscourseURL.routeTo(this.fullSlug);
}
}, },
}, },
}); });

View File

@ -15,6 +15,7 @@ export default Controller.extend({
createdCategory: false, createdCategory: false,
expandedMenu: false, expandedMenu: false,
mobileView: readOnly("site.mobileView"), mobileView: readOnly("site.mobileView"),
parentParams: null,
@on("init") @on("init")
_initPanels() { _initPanels() {

View File

@ -485,6 +485,25 @@ Category.reopenClass({
return ajax(`/c/${slugPath}/find_by_slug.json`); 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) { search(term, opts) {
var limit = 5; var limit = 5;

View File

@ -22,6 +22,16 @@ export default function () {
this.route("topicBySlugOrId", { path: "/t/:slugOrId", resetNamespace: true }); this.route("topicBySlugOrId", { path: "/t/:slugOrId", resetNamespace: true });
this.route("newCategory", { path: "/new-category" }); 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 () { this.route("discovery", { path: "/", resetNamespace: true }, function () {
// legacy route // legacy route
@ -65,8 +75,6 @@ export default function () {
}); });
this.route("categories"); this.route("categories");
this.route("editCategory", { path: "/c/:slug/edit" });
this.route("editChildCategory", { path: "/c/:parentSlug/:slug/edit" });
// legacy routes // legacy routes
this.route("parentCategory", { path: "/c/:slug" }); this.route("parentCategory", { path: "/c/:slug" });

View File

@ -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,
});
},
});

View File

@ -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,
});
},
});

View File

@ -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`);
},
});

View File

@ -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,
});
},
});

View File

@ -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,
});
},
});

View File

@ -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`);
},
});

View File

@ -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")
);
},
});

View File

@ -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,
});
},
});

View File

@ -24,8 +24,8 @@ export default DiscourseRoute.extend({
}, },
renderTemplate() { renderTemplate() {
this.render("edit-category", { this.render("edit-category-tabs", {
controller: "edit-category", controller: "edit-category-tabs",
model: this.currentModel, model: this.currentModel,
}); });
}, },

View File

@ -1,12 +1,12 @@
<div class="edit-category {{if expandedMenu "expanded-menu"}}"> <div class="edit-category {{if expandedMenu "expanded-menu"}}">
<ul class="nav nav-stacked"> <ul class="nav nav-stacked">
{{edit-category-tab panels=panels selectedTab=selectedTab tab="general"}} {{edit-category-tab panels=panels selectedTab=selectedTab params=parentParams tab="general"}}
{{edit-category-tab panels=panels selectedTab=selectedTab tab="security"}} {{edit-category-tab panels=panels selectedTab=selectedTab params=parentParams tab="security"}}
{{edit-category-tab panels=panels selectedTab=selectedTab tab="settings"}} {{edit-category-tab panels=panels selectedTab=selectedTab params=parentParams tab="settings"}}
{{edit-category-tab panels=panels selectedTab=selectedTab tab="images"}} {{edit-category-tab panels=panels selectedTab=selectedTab params=parentParams tab="images"}}
{{edit-category-tab panels=panels selectedTab=selectedTab tab="topic-template"}} {{edit-category-tab panels=panels selectedTab=selectedTab params=parentParams tab="topic-template"}}
{{#if siteSettings.tagging_enabled}} {{#if siteSettings.tagging_enabled}}
{{edit-category-tab panels=panels selectedTab=selectedTab tab="tags"}} {{edit-category-tab panels=panels selectedTab=selectedTab params=parentParams tab="tags"}}
{{/if}} {{/if}}
</ul> </ul>

View File

@ -7,10 +7,7 @@ acceptance("Category Edit - security", function (needs) {
needs.user(); needs.user();
test("default", async (assert) => { test("default", async (assert) => {
await visit("/c/bug"); await visit("/c/bug/edit/security");
await click(".edit-category");
await click("li.edit-category-security a");
const $permissionListItems = find(".permission-list li"); const $permissionListItems = find(".permission-list li");
@ -24,10 +21,8 @@ acceptance("Category Edit - security", function (needs) {
test("removing a permission", async (assert) => { test("removing a permission", async (assert) => {
const availableGroups = selectKit(".available-groups"); 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 .edit-permission");
await availableGroups.expand(); await availableGroups.expand();
@ -51,10 +46,8 @@ acceptance("Category Edit - security", function (needs) {
const availableGroups = selectKit(".available-groups"); const availableGroups = selectKit(".available-groups");
const permissionSelector = selectKit(".permission-selector"); 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 click(".edit-category-tab-security .edit-permission");
await availableGroups.expand(); await availableGroups.expand();
await availableGroups.selectRowByValue("staff"); await availableGroups.selectRowByValue("staff");
@ -76,10 +69,8 @@ acceptance("Category Edit - security", function (needs) {
test("adding a previously removed permission", async (assert) => { test("adding a previously removed permission", async (assert) => {
const availableGroups = selectKit(".available-groups"); 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 .edit-permission");
await click( await click(
".edit-category-tab-security .permission-list li:first-of-type .remove-permission" ".edit-category-tab-security .permission-list li:first-of-type .remove-permission"

View File

@ -2,6 +2,8 @@ import { click, fillIn, visit, currentURL } from "@ember/test-helpers";
import { test } from "qunit"; import { test } from "qunit";
import selectKit from "discourse/tests/helpers/select-kit-helper"; import selectKit from "discourse/tests/helpers/select-kit-helper";
import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import DiscourseURL from "discourse/lib/url";
import sinon from "sinon";
acceptance("Category Edit", function (needs) { acceptance("Category Edit", function (needs) {
needs.user(); needs.user();
@ -11,7 +13,11 @@ acceptance("Category Edit", function (needs) {
await visit("/c/bug"); await visit("/c/bug");
await click("button.edit-category"); 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"); assert.equal(find(".badge-category").text(), "bug");
await fillIn("input.category-name", "testing"); await fillIn("input.category-name", "testing");
@ -22,19 +28,55 @@ acceptance("Category Edit", function (needs) {
await click(".edit-category-topic-template"); await click(".edit-category-topic-template");
await fillIn(".d-editor-input", "this is the new 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"); const searchPriorityChooser = selectKit("#category-search-priority");
await searchPriorityChooser.expand(); await searchPriorityChooser.expand();
await searchPriorityChooser.selectRowByValue(1); await searchPriorityChooser.selectRowByValue(1);
await click("#save-category"); 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) => { test("Error Saving", async (assert) => {
await visit("/c/bug"); await visit("/c/bug/edit/settings");
await click("button.edit-category");
await click(".edit-category-settings");
await fillIn(".email-in", "duplicate@example.com"); await fillIn(".email-in", "duplicate@example.com");
await click("#save-category"); await click("#save-category");
@ -46,13 +88,7 @@ acceptance("Category Edit", function (needs) {
}); });
test("Subcategory list settings", async (assert) => { test("Subcategory list settings", async (assert) => {
const categoryChooser = selectKit( await visit("/c/bug/edit/settings");
".edit-category-tab-general .category-chooser"
);
await visit("/c/bug");
await click("button.edit-category");
await click(".edit-category-settings a");
assert.ok( assert.ok(
!visible(".subcategory-list-style-field"), !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" "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.expand();
await categoryChooser.selectRowByValue(3); await categoryChooser.selectRowByValue(3);
await click(".edit-category-settings a"); await visit("/c/bug/edit/settings");
assert.ok( assert.ok(
!visible(".show-subcategory-list-field"), !visible(".show-subcategory-list-field"),

View File

@ -3,6 +3,7 @@ import { test } from "qunit";
import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import I18n from "I18n"; import I18n from "I18n";
import DiscourseURL from "discourse/lib/url"; import DiscourseURL from "discourse/lib/url";
import sinon from "sinon";
acceptance("Category New", function (needs) { acceptance("Category New", function (needs) {
needs.user(); 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( await click(".edit-category-settings a");
DiscourseURL.redirectedTo, assert.ok(
"/c/testing/11", 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" "it full page redirects after a newly created category"
); );
}); });

View File

@ -4,6 +4,8 @@ import I18n from "I18n";
import { acceptance } from "discourse/tests/helpers/qunit-helpers"; import { acceptance } from "discourse/tests/helpers/qunit-helpers";
import PreloadStore from "discourse/lib/preload-store"; import PreloadStore from "discourse/lib/preload-store";
import { parsePostData } from "discourse/tests/helpers/create-pretender"; import { parsePostData } from "discourse/tests/helpers/create-pretender";
import DiscourseURL from "discourse/lib/url";
import sinon from "sinon";
acceptance("Password Reset", function (needs) { acceptance("Password Reset", function (needs) {
needs.pretender((server, helper) => { needs.pretender((server, helper) => {
@ -84,8 +86,9 @@ acceptance("Password Reset", function (needs) {
); );
await fillIn(".password-reset input", "perf3ctly5ecur3"); await fillIn(".password-reset input", "perf3ctly5ecur3");
sinon.stub(DiscourseURL, "redirectTo");
await click(".password-reset form button"); 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) => { 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"); assert.ok(exists("#new-account-password"), "shows the input");
await fillIn(".password-reset input", "perf3ctly5ecur3"); 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"
);
}); });
}); });

View File

@ -12,7 +12,6 @@ import createPretender, {
} from "discourse/tests/helpers/create-pretender"; } from "discourse/tests/helpers/create-pretender";
import { flushMap } from "discourse/models/store"; import { flushMap } from "discourse/models/store";
import { ScrollingDOMMethods } from "discourse/mixins/scrolling"; import { ScrollingDOMMethods } from "discourse/mixins/scrolling";
import DiscourseURL from "discourse/lib/url";
import { import {
resetSite, resetSite,
applyPretender, applyPretender,
@ -130,11 +129,6 @@ export default function setupTests(app, container) {
site, site,
}); });
DiscourseURL.redirectedTo = null;
DiscourseURL.redirectTo = function (url) {
DiscourseURL.redirectedTo = url;
};
PreloadStore.reset(); PreloadStore.reset();
window.sinon.stub(ScrollingDOMMethods, "screenNotFull"); window.sinon.stub(ScrollingDOMMethods, "screenNotFull");

View File

@ -682,8 +682,8 @@ Discourse::Application.routes.draw do
get "c/:category_slug/find_by_slug" => "categories#find_by_slug" 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/: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/:category_slug/edit(/:tab)" => "categories#find_by_slug", constraints: { format: 'html' }
get "c/:parent_category_slug/:category_slug/edit" => "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 "/new-category" => "categories#show", constraints: { format: 'html' }
get "c/*category_slug_path_with_id.rss" => "list#category_feed", format: :rss get "c/*category_slug_path_with_id.rss" => "list#category_feed", format: :rss