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

View File

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

View File

@ -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;

View File

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

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() {
this.render("edit-category", {
controller: "edit-category",
this.render("edit-category-tabs", {
controller: "edit-category-tabs",
model: this.currentModel,
});
},

View File

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

View File

@ -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"

View File

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

View File

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

View File

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

View File

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

View File

@ -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