UX: Revamp category security tab (#11273)

This commit is contained in:
Penar Musaraj 2020-11-20 10:44:34 -05:00 committed by GitHub
parent dbcf722ab9
commit 7539c2ed7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 454 additions and 199 deletions

View File

@ -0,0 +1,123 @@
import I18n from "I18n";
import Component from "@ember/component";
import discourseComputed, { observes } from "discourse-common/utils/decorators";
import PermissionType from "discourse/models/permission-type";
import { equal, alias } from "@ember/object/computed";
const EVERYONE = "everyone";
export default Component.extend({
classNames: ["permission-row", "row-body"],
canCreate: equal("type", PermissionType.FULL),
everyonePermissionType: alias("everyonePermission.permission_type"),
@discourseComputed("type")
canReply(value) {
return (
value === PermissionType.CREATE_POST || value === PermissionType.FULL
);
},
@discourseComputed("type")
canReplyIcon() {
return this.canReply ? "check-square" : "far-square";
},
@discourseComputed("type")
canCreateIcon() {
return this.canCreate ? "check-square" : "far-square";
},
@discourseComputed("type")
replyGranted() {
return this.type <= PermissionType.CREATE_POST ? "reply-granted" : "";
},
@discourseComputed("type")
createGranted() {
return this.type === PermissionType.FULL ? "create-granted" : "";
},
@observes("everyonePermissionType")
inheritFromEveryone() {
if (this.group_name === EVERYONE) {
return;
}
// groups cannot have a lesser permission than "everyone"
if (this.everyonePermissionType < this.type) {
this.updatePermission(this.everyonePermissionType);
}
},
@discourseComputed("everyonePermissionType", "type")
replyDisabled(everyonePermissionType) {
if (
this.group_name !== EVERYONE &&
everyonePermissionType &&
everyonePermissionType <= PermissionType.CREATE_POST
) {
return true;
}
return false;
},
@discourseComputed("replyDisabled")
replyTooltip() {
return this.replyDisabled
? I18n.t("category.permissions.inherited")
: I18n.t("category.permissions.toggle_reply");
},
@discourseComputed("everyonePermissionType", "type")
createDisabled(everyonePermissionType) {
if (
this.group_name !== EVERYONE &&
everyonePermissionType &&
everyonePermissionType === PermissionType.FULL
) {
return true;
}
return false;
},
@discourseComputed("createDisabled")
createTooltip() {
return this.createDisabled
? I18n.t("category.permissions.inherited")
: I18n.t("category.permissions.toggle_full");
},
updatePermission(type) {
this.category.updatePermission(this.group_name, type);
},
actions: {
removeRow() {
this.category.removePermission(this.group_name);
},
setPermissionReply() {
if (this.type <= PermissionType.CREATE_POST) {
this.updatePermission(PermissionType.READONLY);
} else {
this.updatePermission(PermissionType.CREATE_POST);
}
},
setPermissionFull() {
if (
this.group_name !== EVERYONE &&
this.everyonePermissionType === PermissionType.FULL
) {
return;
}
if (this.type === PermissionType.FULL) {
this.updatePermission(PermissionType.CREATE_POST);
} else {
this.updatePermission(PermissionType.FULL);
}
},
},
});

View File

@ -1,78 +1,39 @@
import { buildCategoryPanel } from "discourse/components/edit-category-panel"; import { buildCategoryPanel } from "discourse/components/edit-category-panel";
import PermissionType from "discourse/models/permission-type"; import PermissionType from "discourse/models/permission-type";
import { on } from "discourse-common/utils/decorators"; import discourseComputed from "discourse-common/utils/decorators";
import { not } from "@ember/object/computed";
export default buildCategoryPanel("security", { export default buildCategoryPanel("security", {
editingPermissions: false,
selectedGroup: null, selectedGroup: null,
selectedPermission: null, noGroupSelected: not("selectedGroup"),
showPendingGroupChangesAlert: false,
interactedWithDropdowns: false,
@on("init") @discourseComputed("category.permissions.@each.permission_type")
_setup() { everyonePermission(permissions) {
this.setProperties({ return permissions.findBy("group_name", "everyone");
selectedGroup: this.get("category.availableGroups.firstObject"),
selectedPermission: this.get(
"category.availablePermissions.firstObject.id"
),
});
}, },
@on("init") @discourseComputed("category.permissions.@each.permission_type")
_registerValidator() { everyoneGrantedFull() {
this.registerValidator(() => { return (
if ( this.everyonePermission &&
!this.showPendingGroupChangesAlert && this.everyonePermission.permission_type === PermissionType.FULL
this.interactedWithDropdowns && );
this.activeTab },
) {
this.set("showPendingGroupChangesAlert", true); @discourseComputed("everyonePermission")
return true; minimumPermission(everyonePermission) {
} return everyonePermission
}); ? everyonePermission.permission_type
: PermissionType.READONLY;
}, },
actions: { actions: {
onSelectGroup(selectedGroup) { onSelectGroup(group_name) {
this.setProperties({
interactedWithDropdowns: true,
selectedGroup,
});
},
onSelectPermission(selectedPermission) {
this.setProperties({
interactedWithDropdowns: true,
selectedPermission,
});
},
editPermissions() {
if (!this.get("category.is_special")) {
this.set("editingPermissions", true);
}
},
addPermission(group, id) {
if (!this.get("category.is_special")) {
this.category.addPermission({ this.category.addPermission({
group_name: group + "", group_name,
permission: PermissionType.create({ id: parseInt(id, 10) }), permission_type: this.minimumPermission,
}); });
}
this.setProperties({
selectedGroup: this.get("category.availableGroups.firstObject"),
showPendingGroupChangesAlert: false,
interactedWithDropdowns: false,
});
},
removePermission(permission) {
if (!this.get("category.is_special")) {
this.category.removePermission(permission);
}
}, },
}, },
}); });

View File

@ -6,6 +6,7 @@ import { propertyEqual } from "discourse/lib/computed";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
import { empty } from "@ember/object/computed"; import { empty } from "@ember/object/computed";
import DiscourseURL from "discourse/lib/url"; import DiscourseURL from "discourse/lib/url";
import { underscore } from "@ember/string";
export default Component.extend({ export default Component.extend({
tagName: "li", tagName: "li",
@ -21,7 +22,7 @@ export default Component.extend({
@discourseComputed("tab") @discourseComputed("tab")
title(tab) { title(tab) {
return I18n.t("category." + tab.replace("-", "_")); return I18n.t(`category.${underscore(tab)}`);
}, },
didInsertElement() { didInsertElement() {

View File

@ -7,6 +7,7 @@ import DiscourseURL from "discourse/lib/url";
import { readOnly } from "@ember/object/computed"; import { readOnly } from "@ember/object/computed";
import PermissionType from "discourse/models/permission-type"; import PermissionType from "discourse/models/permission-type";
import { NotificationLevels } from "discourse/lib/notification-levels"; import { NotificationLevels } from "discourse/lib/notification-levels";
import { underscore } from "@ember/string";
export default Controller.extend({ export default Controller.extend({
selectedTab: "general", selectedTab: "general",
@ -69,6 +70,11 @@ export default Controller.extend({
: I18n.t("category.create"); : I18n.t("category.create");
}, },
@discourseComputed("selectedTab")
selectedTabTitle(tab) {
return I18n.t(`category.${underscore(tab)}`);
},
actions: { actions: {
registerValidator(validator) { registerValidator(validator) {
this.validators.push(validator); this.validators.push(validator);

View File

@ -10,6 +10,8 @@ import Site from "discourse/models/site";
import User from "discourse/models/user"; import User from "discourse/models/user";
import { getOwner } from "discourse-common/lib/get-owner"; import { getOwner } from "discourse-common/lib/get-owner";
const STAFF_GROUP_NAME = "staff";
const Category = RestModel.extend({ const Category = RestModel.extend({
permissions: null, permissions: null,
@ -22,15 +24,13 @@ const Category = RestModel.extend({
this.set("availableGroups", availableGroups); this.set("availableGroups", availableGroups);
const groupPermissions = this.group_permissions; const groupPermissions = this.group_permissions;
if (groupPermissions) { if (groupPermissions) {
this.set( this.set(
"permissions", "permissions",
groupPermissions.map((elem) => { groupPermissions.map((elem) => {
availableGroups.removeObject(elem.group_name); availableGroups.removeObject(elem.group_name);
return { return elem;
group_name: elem.group_name,
permission: PermissionType.create({ id: elem.permission_type }),
};
}) })
); );
} }
@ -231,7 +231,12 @@ const Category = RestModel.extend({
_permissionsForUpdate() { _permissionsForUpdate() {
const permissions = this.permissions; const permissions = this.permissions;
let rval = {}; let rval = {};
permissions.forEach((p) => (rval[p.group_name] = p.permission.id)); if (permissions.length) {
permissions.forEach((p) => (rval[p.group_name] = p.permission_type));
} else {
// empty permissions => staff-only access
rval[STAFF_GROUP_NAME] = PermissionType.FULL;
}
return rval; return rval;
}, },
@ -246,9 +251,20 @@ const Category = RestModel.extend({
this.availableGroups.removeObject(permission.group_name); this.availableGroups.removeObject(permission.group_name);
}, },
removePermission(permission) { removePermission(group_name) {
const permission = this.permissions.findBy("group_name", group_name);
if (permission) {
this.permissions.removeObject(permission); this.permissions.removeObject(permission);
this.availableGroups.addObject(permission.group_name); this.availableGroups.addObject(group_name);
}
},
updatePermission(group_name, type) {
this.permissions.forEach((p, i) => {
if (p.group_name === group_name) {
this.set(`permissions.${i}.permission_type`, type);
}
});
}, },
@discourseComputed("topics") @discourseComputed("topics")

View File

@ -0,0 +1,29 @@
<span class="group-name">
<span class="group-name-label">{{group_name}}</span>
<a class="remove-permission" href {{action "removeRow"}}>
{{d-icon "far-trash-alt"}}
</a>
</span>
<span class="options actionable">
{{d-button
icon="check-square"
class="btn btn-flat see"
disabled=true
}}
{{d-button
icon=canReplyIcon
action=(action "setPermissionReply")
translatedTitle=replyTooltip
class=(concat "btn btn-flat reply-toggle " replyGranted)
disabled=replyDisabled
}}
{{d-button
icon=canCreateIcon
action=(action "setPermissionFull")
translatedTitle=createTooltip
class=(concat "btn btn-flat create-toggle " createGranted)
disabled=createDisabled
}}
</span>

View File

@ -12,14 +12,16 @@
<section class="field"> <section class="field">
<label>{{i18n "category.parent"}}</label> <label>{{i18n "category.parent"}}</label>
{{category-chooser {{category-chooser
rootNone=true
value=category.parent_category_id value=category.parent_category_id
excludeCategoryId=category.id
categories=parentCategories categories=parentCategories
allowSubCategories=true allowSubCategories=true
allowUncategorized=false allowUncategorized=false
allowRestrictedCategories=true allowRestrictedCategories=true
onChange=(action (mut category.parent_category_id)) onChange=(action (mut category.parent_category_id))
options=(hash
excludeCategoryId=category.id
none=true
)
}} }}
</section> </section>
{{/if}} {{/if}}

View File

@ -6,62 +6,50 @@
<p class="warning">{{i18n "category.special_warning"}}</p> <p class="warning">{{i18n "category.special_warning"}}</p>
{{/if}} {{/if}}
{{/if}} {{/if}}
{{#unless category.isUncategorizedCategory}}
<ul class="permission-list"> {{#unless category.is_special}}
<div class="category-permissions-table">
<div class="permission-row row-header">
<span class="group-name">{{i18n "category.permissions.group"}}</span>
<span class="options">
<span class="cell">{{i18n "category.permissions.see"}}</span>
<span class="cell">{{i18n "category.permissions.reply"}}</span>
<span class="cell">{{i18n "category.permissions.create"}}</span>
</span>
</div>
{{#each category.permissions as |p|}} {{#each category.permissions as |p|}}
<li> {{category-permission-row group_name=p.group_name type=p.permission_type category=category everyonePermission=everyonePermission}}
<span class="name"><span class="badge-group">{{p.group_name}}</span></span>
{{html-safe (i18n "category.can")}}
<span class="permission">{{p.permission.description}}</span>
{{#if editingPermissions}}
<a class="remove-permission" href {{action "removePermission" p}}>{{d-icon "times-circle"}}</a>
{{/if}}
</li>
{{/each}} {{/each}}
</ul>
{{#unless category.permissions}}
<div class="permission-row row-empty">
{{i18n "category.permissions.no_groups_selected"}}
</div>
{{/unless}} {{/unless}}
{{#if editingPermissions}}
{{#if category.availableGroups}} {{#if category.availableGroups}}
<div class="add-group">
<span class="group-name">
{{combo-box {{combo-box
class="available-groups" class="available-groups"
content=category.availableGroups content=category.availableGroups
onChange=(action "onSelectGroup") onChange=(action "onSelectGroup")
value=selectedGroup value=null
valueProperty=null valueProperty=null
nameProperty=null nameProperty=null
options=(hash options=(hash
placementStrategy="absolute" none="category.security_add_group"
) )
}} }}
{{combo-box </span>
class="permission-selector"
nameProperty="description"
content=category.availablePermissions
onChange=(action "onSelectPermission")
value=selectedPermission
options=(hash
placementStrategy="absolute"
)
}}
{{d-button
action=(action "addPermission" selectedGroup selectedPermission)
class="btn-primary add-permission"
icon="plus"}}
{{#if showPendingGroupChangesAlert}}
<div class="pending-permission-change-alert">
<div class="arrow-div"></div>
{{i18n "category.pending_permission_change_alert" group=selectedGroup}}
</div> </div>
{{/if}} {{/if}}
</div>
{{#if everyoneGrantedFull}}
<p class="warning">{{i18n "category.permissions.everyone_has_access"}}</p>
{{/if}} {{/if}}
{{else}}
{{#unless category.is_special}}
{{d-button
action=(action "editPermissions")
class="btn-default edit-permission"
label="category.edit_permissions"}}
{{/unless}} {{/unless}}
{{/if}}
</section> </section>
{{plugin-outlet name="category-custom-security" args=(hash category=category) connectorTagName="" tagName="section"}} {{plugin-outlet name="category-custom-security" args=(hash category=category) connectorTagName="" tagName="section"}}

View File

@ -1,6 +1,4 @@
<section> <section>
<h3>{{i18n "category.settings_sections.general"}}</h3>
{{#if showPositionInput}} {{#if showPositionInput}}
<section class="field position-fields"> <section class="field position-fields">
<label for="category-position"> <label for="category-position">

View File

@ -1,2 +1 @@
<label>{{i18n "category.topic_template"}}</label>
{{d-editor value=category.topic_template showLink=showInsertLinkButton}} {{d-editor value=category.topic_template showLink=showInsertLinkButton}}

View File

@ -26,9 +26,13 @@
</ul> </ul>
</div> </div>
<div class="edit-category-content">
<h3>{{selectedTabTitle}}</h3>
{{#each panels as |tab|}} {{#each panels as |tab|}}
{{component tab selectedTab=selectedTab category=model registerValidator=(action "registerValidator")}} {{component tab selectedTab=selectedTab category=model registerValidator=(action "registerValidator")}}
{{/each}} {{/each}}
</div>
<div class="edit-category-footer"> <div class="edit-category-footer">
{{d-button id="save-category" class="btn-primary" disabled=disabled action=(action "saveCategory") label=saveLabel}} {{d-button id="save-category" class="btn-primary" disabled=disabled action=(action "saveCategory") label=saveLabel}}

View File

@ -3,6 +3,7 @@ import { click, visit } 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 I18n from "I18n";
acceptance("Category Edit - security", function (needs) { acceptance("Category Edit - security", function (needs) {
needs.user(); needs.user();
@ -10,13 +11,12 @@ acceptance("Category Edit - security", function (needs) {
test("default", async function (assert) { test("default", async function (assert) {
await visit("/c/bug/edit/security"); await visit("/c/bug/edit/security");
const $firstItem = queryAll(".permission-list li:eq(0)"); const firstRow = queryAll(".row-body").first();
const badgeName = firstRow.find(".group-name-label").text();
const badgeName = $firstItem.find(".badge-group").text();
assert.equal(badgeName, "everyone"); assert.equal(badgeName, "everyone");
const permission = $firstItem.find(".permission").text(); const permission = firstRow.find(".d-icon-check-square");
assert.equal(permission, "Create / Reply / See"); assert.equal(permission.length, 3);
}); });
test("removing a permission", async function (assert) { test("removing a permission", async function (assert) {
@ -24,47 +24,42 @@ acceptance("Category Edit - security", function (needs) {
await visit("/c/bug/edit/security"); await visit("/c/bug/edit/security");
await click(".edit-category-tab-security .edit-permission");
await availableGroups.expand(); await availableGroups.expand();
assert.notOk( assert.notOk(
availableGroups.rowByValue("everyone").exists(), availableGroups.rowByValue("everyone").exists(),
"everyone is already used and is not in the available groups" "everyone is already used and is not in the available groups"
); );
await click( await click(".row-body .remove-permission");
".edit-category-tab-security .permission-list li:first-of-type .remove-permission"
);
await availableGroups.expand(); await availableGroups.expand();
assert.ok( assert.ok(
availableGroups.rowByValue("everyone").exists(), availableGroups.rowByValue("everyone").exists(),
"everyone has been removed and appears in the available groups" "everyone has been removed and appears in the available groups"
); );
assert.ok(
queryAll(".row-empty").text(),
I18n.t("category.permissions.no_groups_selected"),
"shows message when no groups are selected"
);
}); });
test("adding a permission", async function (assert) { test("adding a permission", async function (assert) {
const availableGroups = selectKit(".available-groups"); const availableGroups = selectKit(".available-groups");
const permissionSelector = selectKit(".permission-selector");
await visit("/c/bug/edit/security"); await visit("/c/bug/edit/security");
await click(".edit-category-tab-security .edit-permission");
await availableGroups.expand(); await availableGroups.expand();
await availableGroups.selectRowByValue("staff"); await availableGroups.selectRowByValue("staff");
await permissionSelector.expand();
await permissionSelector.selectRowByValue("2");
await click(".edit-category-tab-security .add-permission");
const $addedPermissionItem = queryAll( const addedRow = queryAll(".row-body").last();
".edit-category-tab-security .permission-list li:nth-child(2)"
assert.equal(addedRow.find(".group-name-label").text(), "staff");
assert.equal(
addedRow.find(".d-icon-check-square").length,
3,
"new row permissions match default 'everyone' permissions"
); );
const badgeName = $addedPermissionItem.find(".badge-group").text();
assert.equal(badgeName, "staff");
const permission = $addedPermissionItem.find(".permission").text();
assert.equal(permission, "Reply / See");
}); });
test("adding a previously removed permission", async function (assert) { test("adding a previously removed permission", async function (assert) {
@ -72,33 +67,103 @@ acceptance("Category Edit - security", function (needs) {
await visit("/c/bug/edit/security"); await visit("/c/bug/edit/security");
await click(".edit-category-tab-security .edit-permission"); await click(".row-body .remove-permission");
await click(
".edit-category-tab-security .permission-list li:first-of-type .remove-permission"
);
assert.equal( assert.equal(
queryAll(".edit-category-tab-security .permission-list li").length, queryAll(".row-body").length,
0, 0,
"it removes the permission from the list" "removes the permission from the list"
); );
await availableGroups.expand(); await availableGroups.expand();
await availableGroups.selectRowByValue("everyone"); await availableGroups.selectRowByValue("everyone");
await click(".edit-category-tab-security .add-permission");
assert.equal( assert.equal(
queryAll(".edit-category-tab-security .permission-list li").length, queryAll(".row-body").length,
1, 1,
"it adds the permission to the list" "adds back the permission tp the list"
); );
const $firstItem = queryAll(".permission-list li:eq(0)"); const firstRow = queryAll(".row-body").first();
const badgeName = $firstItem.find(".badge-group").text(); assert.equal(firstRow.find(".group-name-label").text(), "everyone");
assert.equal(badgeName, "everyone"); assert.equal(
firstRow.find(".d-icon-check-square").length,
1,
"adds only 'See' permission for a new row"
);
});
const permission = $firstItem.find(".permission").text(); test("editing permissions", async function (assert) {
assert.equal(permission, "Create / Reply / See"); const availableGroups = selectKit(".available-groups");
await visit("/c/bug/edit/security");
const everyoneRow = queryAll(".row-body").first();
assert.equal(
everyoneRow.find(".reply-granted, .create-granted").length,
2,
"everyone has full permissions by default"
);
await availableGroups.expand();
await availableGroups.selectRowByValue("staff");
const staffRow = queryAll(".row-body").last();
assert.equal(
staffRow.find(".reply-granted, .create-granted").length,
2,
"staff group also has full permissions"
);
await click(everyoneRow.find(".reply-toggle"));
assert.equal(
everyoneRow.find(".reply-granted, .create-granted").length,
0,
"everyone does not have reply or create"
);
assert.equal(
staffRow.find(".reply-granted, .create-granted").length,
2,
"staff group still has full permissions"
);
await click(staffRow.find(".reply-toggle"));
assert.equal(
everyoneRow.find(".reply-granted, .create-granted").length,
0,
"everyone permission unchanged"
);
assert.equal(
staffRow.find(".reply-granted").length,
0,
"staff does not have reply permission"
);
assert.equal(
staffRow.find(".create-granted").length,
0,
"staff does not have create permission"
);
await click(everyoneRow.find(".create-toggle"));
assert.equal(
everyoneRow.find(".reply-granted, .create-granted").length,
2,
"everyone has full permissions"
);
assert.equal(
staffRow.find(".reply-granted, .create-granted").length,
2,
"staff group has full permissions (inherited from everyone)"
);
}); });
}); });

View File

@ -27,6 +27,10 @@ div.edit-category {
} }
} }
.edit-category-content {
grid-area: content;
}
#list-area & h2 { #list-area & h2 {
margin: 0; margin: 0;
} }
@ -35,16 +39,16 @@ div.edit-category {
margin-bottom: 1em; margin-bottom: 1em;
} }
.edit-category-tab-general {
.category-chooser {
width: unquote("min(340px, 90%)");
}
.warning { .warning {
background-color: var(--tertiary-low); background-color: var(--tertiary-low);
padding: 0.5em 2.5em 0.5em 1em; padding: 0.5em 2.5em 0.5em 1em;
margin-top: 0; margin-top: 0;
} }
.edit-category-tab-general {
.category-chooser {
width: unquote("min(340px, 90%)");
}
} }
.edit-category-tab-security { .edit-category-tab-security {
@ -71,11 +75,6 @@ div.edit-category {
} }
} }
.add-permission {
position: relative;
top: 0.1em;
}
.permission-list { .permission-list {
list-style: none; list-style: none;
margin: 0 0 30px; margin: 0 0 30px;
@ -147,7 +146,7 @@ div.edit-category {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-self: start; align-self: start;
padding-bottom: 2em; padding: 0 1.5em 2em 0;
.disable-info { .disable-info {
position: relative; position: relative;
@ -175,3 +174,70 @@ div.edit-category {
} }
} }
} }
.category-permissions-table {
max-width: 450px;
margin-bottom: 2em;
.permission-row {
border-bottom: 1px solid var(--primary-low);
display: flex;
&.row-header {
font-weight: bold;
border-bottom: 2px solid var(--primary-low);
}
.group-name,
.options {
display: flex;
box-sizing: border-box;
text-align: center;
width: 50%;
margin: 0px;
align-items: center;
}
.group-name {
text-align: left;
padding: 0.5em;
padding-left: 0;
.group-name-label {
@include ellipsis;
}
}
.cell,
.btn-flat {
width: 33%;
padding: 0.5em;
}
.btn-flat:hover {
background-color: transparent;
}
.btn-flat .d-icon-check-square,
.btn-flat:hover .d-icon-check-square {
color: var(--success);
}
}
.remove-permission {
margin-left: 0.5em;
padding: 0.15em;
color: var(--danger);
&:hover {
color: var(--danger-hover);
}
}
.row-empty {
padding: 0.5em 0;
}
.row-empty {
color: var(--primary-medium);
}
.add-group {
margin: 1em 0;
.group-name {
width: 100%;
}
}
}

View File

@ -39,24 +39,6 @@ div.edit-category {
} }
} }
.edit-category-tab,
.edit-category-footer {
background-color: var(--secondary);
transition: transform 0.2s ease;
transform: translateX(0);
}
&.expanded-menu {
.edit-category-tab,
.edit-category-footer {
transform: translateX(45%);
}
.nav-stacked {
left: 0px;
}
}
.edit-category-title { .edit-category-title {
justify-content: start; justify-content: start;
align-items: center; align-items: center;
@ -74,7 +56,11 @@ div.edit-category {
} }
} }
.edit-category-content {
padding: 0.5em;
}
.edit-category-footer { .edit-category-footer {
padding-bottom: 2em; padding: 0 0.5em 2em 0.5em;
} }
} }

View File

@ -2961,6 +2961,17 @@ en:
change_in_category_topic: "Edit Description" change_in_category_topic: "Edit Description"
already_used: "This color has been used by another category" already_used: "This color has been used by another category"
security: "Security" security: "Security"
security_add_group: "Add a group"
permissions:
group: "Group"
see: "See"
reply: "Reply"
create: "Create"
no_groups_selected: "No groups have been granted access; this category will only be visible to staff."
everyone_has_access: "This category is public, everyone can see, reply and create posts. To restrict permissions, remove one or more of the permissions granted to the \"everyone\" group."
toggle_reply: "Toggle Reply permission"
toggle_full: "Toggle Create permission"
inherited: "This permission is inherited from \"everyone\""
special_warning: "Warning: This category is a pre-seeded category and the security settings cannot be edited. If you do not wish to use this category, delete it instead of repurposing it." special_warning: "Warning: This category is a pre-seeded category and the security settings cannot be edited. If you do not wish to use this category, delete it instead of repurposing it."
uncategorized_security_warning: "This category is special. It is intended as holding area for topics that have no category; it cannot have security settings." uncategorized_security_warning: "This category is special. It is intended as holding area for topics that have no category; it cannot have security settings."
uncategorized_general_warning: 'This category is special. It is used as the default category for new topics that do not have a category selected. If you want to prevent this behavior and force category selection, <a href="%{settingLink}">please disable the setting here</a>. If you want to change the name or description, go to <a href="%{customizeLink}">Customize / Text Content</a>.' uncategorized_general_warning: 'This category is special. It is used as the default category for new topics that do not have a category selected. If you want to prevent this behavior and force category selection, <a href="%{settingLink}">please disable the setting here</a>. If you want to change the name or description, go to <a href="%{customizeLink}">Customize / Text Content</a>.'