Improve permissions page

- Introduce the concept of "required permissions" - basically a permission dependency tree. In order for a group to be granted one permission, they must also have another.
- Improve redraw performance by not building dropdown menu contents until dropdown is opened

ref #904
This commit is contained in:
Toby Zerner 2016-05-27 12:42:19 +09:30
parent 1177880483
commit 240aa9e83b
7 changed files with 257 additions and 115 deletions

167
js/admin/dist/app.js vendored
View File

@ -16609,6 +16609,22 @@ System.register('flarum/app', ['flarum/App', 'flarum/initializers/store', 'flaru
app.extensionSettings = {}; app.extensionSettings = {};
app.getRequiredPermissions = function (permission) {
var required = [];
if (permission === 'startDiscussion' || permission.indexOf('discussion.') === 0) {
required.push('viewDiscussions');
}
if (permission === 'discussion.delete') {
required.push('discussion.hide');
}
if (permission === 'discussion.deletePosts') {
required.push('discussion.editPosts');
}
return required;
};
_export('default', app); _export('default', app);
} }
}; };
@ -18080,13 +18096,18 @@ System.register('flarum/components/Dropdown', ['flarum/Component', 'flarum/helpe
} }
babelHelpers.createClass(Dropdown, [{ babelHelpers.createClass(Dropdown, [{
key: 'init',
value: function init() {
this.showing = false;
}
}, {
key: 'view', key: 'view',
value: function view() { value: function view() {
var items = this.props.children ? listItems(this.props.children) : []; var items = this.props.children ? listItems(this.props.children) : [];
return m( return m(
'div', 'div',
{ className: 'ButtonGroup Dropdown dropdown ' + this.props.className + ' itemCount' + items.length }, { className: 'ButtonGroup Dropdown dropdown ' + this.props.className + ' itemCount' + items.length + (this.showing ? ' open' : '') },
this.getButton(), this.getButton(),
this.getMenu(items) this.getMenu(items)
); );
@ -18102,25 +18123,32 @@ System.register('flarum/components/Dropdown', ['flarum/Component', 'flarum/helpe
// bottom of the viewport. If it does, we will apply class to make it show // bottom of the viewport. If it does, we will apply class to make it show
// above the toggle button instead of below it. // above the toggle button instead of below it.
this.$().on('shown.bs.dropdown', function () { this.$().on('shown.bs.dropdown', function () {
_this2.showing = true;
if (_this2.props.onshow) {
_this2.props.onshow();
}
m.redraw();
var $menu = _this2.$('.Dropdown-menu'); var $menu = _this2.$('.Dropdown-menu');
var isRight = $menu.hasClass('Dropdown-menu--right'); var isRight = $menu.hasClass('Dropdown-menu--right');
$menu.removeClass('Dropdown-menu--top Dropdown-menu--right'); $menu.removeClass('Dropdown-menu--top Dropdown-menu--right');
$menu.toggleClass('Dropdown-menu--top', $menu.offset().top + $menu.height() > $(window).scrollTop() + $(window).height()); $menu.toggleClass('Dropdown-menu--top', $menu.offset().top + $menu.height() > $(window).scrollTop() + $(window).height());
$menu.toggleClass('Dropdown-menu--right', isRight || $menu.offset().left + $menu.width() > $(window).scrollLeft() + $(window).width()); $menu.toggleClass('Dropdown-menu--right', isRight || $menu.offset().left + $menu.width() > $(window).scrollLeft() + $(window).width());
if (_this2.props.onshow) {
_this2.props.onshow();
m.redraw();
}
}); });
this.$().on('hidden.bs.dropdown', function () { this.$().on('hidden.bs.dropdown', function () {
_this2.showing = false;
if (_this2.props.onhide) { if (_this2.props.onhide) {
_this2.props.onhide(); _this2.props.onhide();
m.redraw();
} }
m.redraw();
}); });
} }
}, { }, {
@ -19513,8 +19541,8 @@ System.register('flarum/components/Page', ['flarum/Component'], function (_expor
});; });;
'use strict'; 'use strict';
System.register('flarum/components/PermissionDropdown', ['flarum/components/Dropdown', 'flarum/components/Button', 'flarum/components/Separator', 'flarum/models/Group', 'flarum/components/GroupBadge'], function (_export, _context) { System.register('flarum/components/PermissionDropdown', ['flarum/components/Dropdown', 'flarum/components/Button', 'flarum/components/Separator', 'flarum/models/Group', 'flarum/components/Badge', 'flarum/components/GroupBadge'], function (_export, _context) {
var Dropdown, Button, Separator, Group, GroupBadge, PermissionDropdown; var Dropdown, Button, Separator, Group, Badge, GroupBadge, PermissionDropdown;
function badgeForId(id) { function badgeForId(id) {
@ -19523,6 +19551,30 @@ System.register('flarum/components/PermissionDropdown', ['flarum/components/Drop
return group ? GroupBadge.component({ group: group, label: null }) : ''; return group ? GroupBadge.component({ group: group, label: null }) : '';
} }
function filterByRequiredPermissions(groupIds, permission) {
app.getRequiredPermissions(permission).forEach(function (required) {
var restrictToGroupIds = app.data.permissions[required] || [];
if (restrictToGroupIds.indexOf(Group.GUEST_ID) !== -1) {
// do nothing
} else if (restrictToGroupIds.indexOf(Group.MEMBER_ID) !== -1) {
groupIds = groupIds.filter(function (id) {
return id !== Group.GUEST_ID;
});
} else if (groupIds.indexOf(Group.MEMBER_ID) !== -1) {
groupIds = restrictToGroupIds;
} else {
groupIds = restrictToGroupIds.filter(function (id) {
return groupIds.indexOf(id) !== -1;
});
}
groupIds = filterByRequiredPermissions(groupIds, required);
});
return groupIds;
}
return { return {
setters: [function (_flarumComponentsDropdown) { setters: [function (_flarumComponentsDropdown) {
Dropdown = _flarumComponentsDropdown.default; Dropdown = _flarumComponentsDropdown.default;
@ -19532,6 +19584,8 @@ System.register('flarum/components/PermissionDropdown', ['flarum/components/Drop
Separator = _flarumComponentsSeparator.default; Separator = _flarumComponentsSeparator.default;
}, function (_flarumModelsGroup) { }, function (_flarumModelsGroup) {
Group = _flarumModelsGroup.default; Group = _flarumModelsGroup.default;
}, function (_flarumComponentsBadge) {
Badge = _flarumComponentsBadge.default;
}, function (_flarumComponentsGroupBadge) { }, function (_flarumComponentsGroupBadge) {
GroupBadge = _flarumComponentsGroupBadge.default; GroupBadge = _flarumComponentsGroupBadge.default;
}], }],
@ -19552,56 +19606,64 @@ System.register('flarum/components/PermissionDropdown', ['flarum/components/Drop
this.props.children = []; this.props.children = [];
var groupIds = app.data.permissions[this.props.permission] || []; var groupIds = app.data.permissions[this.props.permission] || [];
groupIds = filterByRequiredPermissions(groupIds, this.props.permission);
var everyone = groupIds.indexOf(Group.GUEST_ID) !== -1; var everyone = groupIds.indexOf(Group.GUEST_ID) !== -1;
var members = groupIds.indexOf(Group.MEMBER_ID) !== -1; var members = groupIds.indexOf(Group.MEMBER_ID) !== -1;
var adminGroup = app.store.getById('groups', Group.ADMINISTRATOR_ID); var adminGroup = app.store.getById('groups', Group.ADMINISTRATOR_ID);
if (everyone) { if (everyone) {
this.props.label = app.translator.trans('core.admin.permissions_controls.everyone_button'); this.props.label = Badge.component({ icon: 'globe' });
} else if (members) { } else if (members) {
this.props.label = app.translator.trans('core.admin.permissions_controls.members_button'); this.props.label = Badge.component({ icon: 'user' });
} else { } else {
this.props.label = [badgeForId(Group.ADMINISTRATOR_ID), groupIds.map(badgeForId)]; this.props.label = [badgeForId(Group.ADMINISTRATOR_ID), groupIds.map(badgeForId)];
} }
if (this.props.allowGuest) { if (this.showing) {
if (this.props.allowGuest) {
this.props.children.push(Button.component({
children: [Badge.component({ icon: 'globe' }), ' ', app.translator.trans('core.admin.permissions_controls.everyone_button')],
icon: everyone ? 'check' : true,
onclick: function onclick() {
return _this2.save([Group.GUEST_ID]);
},
disabled: this.isGroupDisabled(Group.GUEST_ID)
}));
}
this.props.children.push(Button.component({ this.props.children.push(Button.component({
children: app.translator.trans('core.admin.permissions_controls.everyone_button'), children: [Badge.component({ icon: 'user' }), ' ', app.translator.trans('core.admin.permissions_controls.members_button')],
icon: everyone ? 'check' : true, icon: members ? 'check' : true,
onclick: function onclick() { onclick: function onclick() {
return _this2.save([Group.GUEST_ID]); return _this2.save([Group.MEMBER_ID]);
} },
})); disabled: this.isGroupDisabled(Group.MEMBER_ID)
} }), Separator.component(), Button.component({
children: [badgeForId(adminGroup.id()), ' ', adminGroup.namePlural()],
this.props.children.push(Button.component({ icon: !everyone && !members ? 'check' : true,
children: app.translator.trans('core.admin.permissions_controls.members_button'), disabled: !everyone && !members,
icon: members ? 'check' : true,
onclick: function onclick() {
return _this2.save([Group.MEMBER_ID]);
}
}), Separator.component(), Button.component({
children: [GroupBadge.component({ group: adminGroup, label: null }), ' ', adminGroup.namePlural()],
icon: !everyone && !members ? 'check' : true,
disabled: !everyone && !members,
onclick: function onclick(e) {
if (e.shiftKey) e.stopPropagation();
_this2.save([]);
}
}));
[].push.apply(this.props.children, app.store.all('groups').filter(function (group) {
return [Group.ADMINISTRATOR_ID, Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1;
}).map(function (group) {
return Button.component({
children: [GroupBadge.component({ group: group, label: null }), ' ', group.namePlural()],
icon: groupIds.indexOf(group.id()) !== -1 ? 'check' : true,
onclick: function onclick(e) { onclick: function onclick(e) {
if (e.shiftKey) e.stopPropagation(); if (e.shiftKey) e.stopPropagation();
_this2.toggle(group.id()); _this2.save([]);
} }
}); }));
}));
[].push.apply(this.props.children, app.store.all('groups').filter(function (group) {
return [Group.ADMINISTRATOR_ID, Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1;
}).map(function (group) {
return Button.component({
children: [badgeForId(group.id()), ' ', group.namePlural()],
icon: groupIds.indexOf(group.id()) !== -1 ? 'check' : true,
onclick: function onclick(e) {
if (e.shiftKey) e.stopPropagation();
_this2.toggle(group.id());
},
disabled: _this2.isGroupDisabled(group.id()) && _this2.isGroupDisabled(Group.MEMBER_ID) && _this2.isGroupDisabled(Group.GUEST_ID)
});
}));
}
return babelHelpers.get(Object.getPrototypeOf(PermissionDropdown.prototype), 'view', this).call(this); return babelHelpers.get(Object.getPrototypeOf(PermissionDropdown.prototype), 'view', this).call(this);
} }
@ -19638,6 +19700,11 @@ System.register('flarum/components/PermissionDropdown', ['flarum/components/Drop
this.save(groupIds); this.save(groupIds);
} }
}, {
key: 'isGroupDisabled',
value: function isGroupDisabled(id) {
return filterByRequiredPermissions([id], this.props.permission).indexOf(id) === -1;
}
}], [{ }], [{
key: 'initProps', key: 'initProps',
value: function initProps(props) { value: function initProps(props) {
@ -19749,7 +19816,7 @@ System.register('flarum/components/PermissionGrid', ['flarum/Component', 'flarum
m( m(
'th', 'th',
null, null,
child.icon ? icon(child.icon) : '', icon(child.icon),
child.label child.label
), ),
permissionCells(child), permissionCells(child),
@ -19871,10 +19938,10 @@ System.register('flarum/components/PermissionGrid', ['flarum/Component', 'flarum
value: function moderateItems() { value: function moderateItems() {
var items = new ItemList(); var items = new ItemList();
items.add('viewPostIps', { items.add('viewIpsPosts', {
icon: 'bullseye', icon: 'bullseye',
label: app.translator.trans('core.admin.permissions.view_post_ips_label'), label: app.translator.trans('core.admin.permissions.view_post_ips_label'),
permission: 'discussion.viewPostIps' permission: 'discussion.viewIpsPosts'
}, 110); }, 110);
items.add('renameDiscussions', { items.add('renameDiscussions', {
@ -23100,11 +23167,13 @@ System.register('flarum/utils/string', [], function (_export, _context) {
_export('slug', slug); _export('slug', slug);
function getPlainContent(string) { function getPlainContent(string) {
var dom = $('<div/>').html(string.replace(/(<\/p>|<br>)/g, '$1 &nbsp;')); var html = string.replace(/(<\/p>|<br>)/g, '$1 &nbsp;').replace(/<img\b[^>]*>/ig, ' ');
var dom = $('<div/>').html(html);
dom.find(getPlainContent.removeSelectors.join(',')).remove(); dom.find(getPlainContent.removeSelectors.join(',')).remove();
return dom.text(); return dom.text().replace(/\s+/g, ' ').trim();
} }
/** /**

View File

@ -14,4 +14,20 @@ app.initializers.add('boot', boot, -100);
app.extensionSettings = {}; app.extensionSettings = {};
app.getRequiredPermissions = function(permission) {
const required = [];
if (permission === 'startDiscussion' || permission.indexOf('discussion.') === 0) {
required.push('viewDiscussions');
}
if (permission === 'discussion.delete') {
required.push('discussion.hide');
}
if (permission === 'discussion.deletePosts') {
required.push('discussion.editPosts');
}
return required;
};
export default app; export default app;

View File

@ -2,6 +2,7 @@ import Dropdown from 'flarum/components/Dropdown';
import Button from 'flarum/components/Button'; import Button from 'flarum/components/Button';
import Separator from 'flarum/components/Separator'; import Separator from 'flarum/components/Separator';
import Group from 'flarum/models/Group'; import Group from 'flarum/models/Group';
import Badge from 'flarum/components/Badge';
import GroupBadge from 'flarum/components/GroupBadge'; import GroupBadge from 'flarum/components/GroupBadge';
function badgeForId(id) { function badgeForId(id) {
@ -10,6 +11,27 @@ function badgeForId(id) {
return group ? GroupBadge.component({group, label: null}) : ''; return group ? GroupBadge.component({group, label: null}) : '';
} }
function filterByRequiredPermissions(groupIds, permission) {
app.getRequiredPermissions(permission)
.forEach(required => {
const restrictToGroupIds = app.data.permissions[required] || [];
if (restrictToGroupIds.indexOf(Group.GUEST_ID) !== -1) {
// do nothing
} else if (restrictToGroupIds.indexOf(Group.MEMBER_ID) !== -1) {
groupIds = groupIds.filter(id => id !== Group.GUEST_ID);
} else if (groupIds.indexOf(Group.MEMBER_ID) !== -1) {
groupIds = restrictToGroupIds;
} else {
groupIds = restrictToGroupIds.filter(id => groupIds.indexOf(id) !== -1);
}
groupIds = filterByRequiredPermissions(groupIds, required);
});
return groupIds;
}
export default class PermissionDropdown extends Dropdown { export default class PermissionDropdown extends Dropdown {
static initProps(props) { static initProps(props) {
super.initProps(props); super.initProps(props);
@ -21,15 +43,18 @@ export default class PermissionDropdown extends Dropdown {
view() { view() {
this.props.children = []; this.props.children = [];
const groupIds = app.data.permissions[this.props.permission] || []; let groupIds = app.data.permissions[this.props.permission] || [];
groupIds = filterByRequiredPermissions(groupIds, this.props.permission);
const everyone = groupIds.indexOf(Group.GUEST_ID) !== -1; const everyone = groupIds.indexOf(Group.GUEST_ID) !== -1;
const members = groupIds.indexOf(Group.MEMBER_ID) !== -1; const members = groupIds.indexOf(Group.MEMBER_ID) !== -1;
const adminGroup = app.store.getById('groups', Group.ADMINISTRATOR_ID); const adminGroup = app.store.getById('groups', Group.ADMINISTRATOR_ID);
if (everyone) { if (everyone) {
this.props.label = app.translator.trans('core.admin.permissions_controls.everyone_button'); this.props.label = Badge.component({icon: 'globe'});
} else if (members) { } else if (members) {
this.props.label = app.translator.trans('core.admin.permissions_controls.members_button'); this.props.label = Badge.component({icon: 'user'});
} else { } else {
this.props.label = [ this.props.label = [
badgeForId(Group.ADMINISTRATOR_ID), badgeForId(Group.ADMINISTRATOR_ID),
@ -37,50 +62,55 @@ export default class PermissionDropdown extends Dropdown {
]; ];
} }
if (this.props.allowGuest) { if (this.showing) {
if (this.props.allowGuest) {
this.props.children.push(
Button.component({
children: [Badge.component({icon: 'globe'}), ' ', app.translator.trans('core.admin.permissions_controls.everyone_button')],
icon: everyone ? 'check' : true,
onclick: () => this.save([Group.GUEST_ID]),
disabled: this.isGroupDisabled(Group.GUEST_ID)
})
);
}
this.props.children.push( this.props.children.push(
Button.component({ Button.component({
children: app.translator.trans('core.admin.permissions_controls.everyone_button'), children: [Badge.component({icon: 'user'}), ' ', app.translator.trans('core.admin.permissions_controls.members_button')],
icon: everyone ? 'check' : true, icon: members ? 'check' : true,
onclick: () => this.save([Group.GUEST_ID]) onclick: () => this.save([Group.MEMBER_ID]),
disabled: this.isGroupDisabled(Group.MEMBER_ID)
}),
Separator.component(),
Button.component({
children: [badgeForId(adminGroup.id()), ' ', adminGroup.namePlural()],
icon: !everyone && !members ? 'check' : true,
disabled: !everyone && !members,
onclick: e => {
if (e.shiftKey) e.stopPropagation();
this.save([]);
}
}) })
); );
[].push.apply(
this.props.children,
app.store.all('groups')
.filter(group => [Group.ADMINISTRATOR_ID, Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
.map(group => Button.component({
children: [badgeForId(group.id()), ' ', group.namePlural()],
icon: groupIds.indexOf(group.id()) !== -1 ? 'check' : true,
onclick: (e) => {
if (e.shiftKey) e.stopPropagation();
this.toggle(group.id());
},
disabled: this.isGroupDisabled(group.id()) && this.isGroupDisabled(Group.MEMBER_ID) && this.isGroupDisabled(Group.GUEST_ID)
}))
);
} }
this.props.children.push(
Button.component({
children: app.translator.trans('core.admin.permissions_controls.members_button'),
icon: members ? 'check' : true,
onclick: () => this.save([Group.MEMBER_ID])
}),
Separator.component(),
Button.component({
children: [GroupBadge.component({group: adminGroup, label: null}), ' ', adminGroup.namePlural()],
icon: !everyone && !members ? 'check' : true,
disabled: !everyone && !members,
onclick: e => {
if (e.shiftKey) e.stopPropagation();
this.save([]);
}
})
);
[].push.apply(
this.props.children,
app.store.all('groups')
.filter(group => [Group.ADMINISTRATOR_ID, Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
.map(group => Button.component({
children: [GroupBadge.component({group, label: null}), ' ', group.namePlural()],
icon: groupIds.indexOf(group.id()) !== -1 ? 'check' : true,
onclick: (e) => {
if (e.shiftKey) e.stopPropagation();
this.toggle(group.id());
}
}))
);
return super.view(); return super.view();
} }
@ -112,4 +142,8 @@ export default class PermissionDropdown extends Dropdown {
this.save(groupIds); this.save(groupIds);
} }
isGroupDisabled(id) {
return filterByRequiredPermissions([id], this.props.permission).indexOf(id) === -1;
}
} }

View File

@ -44,7 +44,7 @@ export default class PermissionGrid extends Component {
</tr> </tr>
{section.children.map(child => ( {section.children.map(child => (
<tr className="PermissionGrid-child"> <tr className="PermissionGrid-child">
<th>{child.icon ? icon(child.icon) : ''}{child.label}</th> <th>{icon(child.icon)}{child.label}</th>
{permissionCells(child)} {permissionCells(child)}
<td/> <td/>
</tr> </tr>

26
js/forum/dist/app.js vendored
View File

@ -21457,13 +21457,18 @@ System.register('flarum/components/Dropdown', ['flarum/Component', 'flarum/helpe
} }
babelHelpers.createClass(Dropdown, [{ babelHelpers.createClass(Dropdown, [{
key: 'init',
value: function init() {
this.showing = false;
}
}, {
key: 'view', key: 'view',
value: function view() { value: function view() {
var items = this.props.children ? listItems(this.props.children) : []; var items = this.props.children ? listItems(this.props.children) : [];
return m( return m(
'div', 'div',
{ className: 'ButtonGroup Dropdown dropdown ' + this.props.className + ' itemCount' + items.length }, { className: 'ButtonGroup Dropdown dropdown ' + this.props.className + ' itemCount' + items.length + (this.showing ? ' open' : '') },
this.getButton(), this.getButton(),
this.getMenu(items) this.getMenu(items)
); );
@ -21479,25 +21484,32 @@ System.register('flarum/components/Dropdown', ['flarum/Component', 'flarum/helpe
// bottom of the viewport. If it does, we will apply class to make it show // bottom of the viewport. If it does, we will apply class to make it show
// above the toggle button instead of below it. // above the toggle button instead of below it.
this.$().on('shown.bs.dropdown', function () { this.$().on('shown.bs.dropdown', function () {
_this2.showing = true;
if (_this2.props.onshow) {
_this2.props.onshow();
}
m.redraw();
var $menu = _this2.$('.Dropdown-menu'); var $menu = _this2.$('.Dropdown-menu');
var isRight = $menu.hasClass('Dropdown-menu--right'); var isRight = $menu.hasClass('Dropdown-menu--right');
$menu.removeClass('Dropdown-menu--top Dropdown-menu--right'); $menu.removeClass('Dropdown-menu--top Dropdown-menu--right');
$menu.toggleClass('Dropdown-menu--top', $menu.offset().top + $menu.height() > $(window).scrollTop() + $(window).height()); $menu.toggleClass('Dropdown-menu--top', $menu.offset().top + $menu.height() > $(window).scrollTop() + $(window).height());
$menu.toggleClass('Dropdown-menu--right', isRight || $menu.offset().left + $menu.width() > $(window).scrollLeft() + $(window).width()); $menu.toggleClass('Dropdown-menu--right', isRight || $menu.offset().left + $menu.width() > $(window).scrollLeft() + $(window).width());
if (_this2.props.onshow) {
_this2.props.onshow();
m.redraw();
}
}); });
this.$().on('hidden.bs.dropdown', function () { this.$().on('hidden.bs.dropdown', function () {
_this2.showing = false;
if (_this2.props.onhide) { if (_this2.props.onhide) {
_this2.props.onhide(); _this2.props.onhide();
m.redraw();
} }
m.redraw();
}); });
} }
}, { }, {

View File

@ -29,11 +29,15 @@ export default class Dropdown extends Component {
props.caretIcon = typeof props.caretIcon !== 'undefined' ? props.caretIcon : 'caret-down'; props.caretIcon = typeof props.caretIcon !== 'undefined' ? props.caretIcon : 'caret-down';
} }
init() {
this.showing = false;
}
view() { view() {
const items = this.props.children ? listItems(this.props.children) : []; const items = this.props.children ? listItems(this.props.children) : [];
return ( return (
<div className={'ButtonGroup Dropdown dropdown ' + this.props.className + ' itemCount' + items.length}> <div className={'ButtonGroup Dropdown dropdown ' + this.props.className + ' itemCount' + items.length + (this.showing ? ' open' : '')}>
{this.getButton()} {this.getButton()}
{this.getMenu(items)} {this.getMenu(items)}
</div> </div>
@ -47,8 +51,17 @@ export default class Dropdown extends Component {
// bottom of the viewport. If it does, we will apply class to make it show // bottom of the viewport. If it does, we will apply class to make it show
// above the toggle button instead of below it. // above the toggle button instead of below it.
this.$().on('shown.bs.dropdown', () => { this.$().on('shown.bs.dropdown', () => {
this.showing = true;
if (this.props.onshow) {
this.props.onshow();
}
m.redraw();
const $menu = this.$('.Dropdown-menu'); const $menu = this.$('.Dropdown-menu');
const isRight = $menu.hasClass('Dropdown-menu--right'); const isRight = $menu.hasClass('Dropdown-menu--right');
$menu.removeClass('Dropdown-menu--top Dropdown-menu--right'); $menu.removeClass('Dropdown-menu--top Dropdown-menu--right');
$menu.toggleClass( $menu.toggleClass(
@ -60,18 +73,16 @@ export default class Dropdown extends Component {
'Dropdown-menu--right', 'Dropdown-menu--right',
isRight || $menu.offset().left + $menu.width() > $(window).scrollLeft() + $(window).width() isRight || $menu.offset().left + $menu.width() > $(window).scrollLeft() + $(window).width()
); );
if (this.props.onshow) {
this.props.onshow();
m.redraw();
}
}); });
this.$().on('hidden.bs.dropdown', () => { this.$().on('hidden.bs.dropdown', () => {
this.showing = false;
if (this.props.onhide) { if (this.props.onhide) {
this.props.onhide(); this.props.onhide();
m.redraw();
} }
m.redraw();
}); });
} }

View File

@ -39,7 +39,7 @@
white-space: nowrap; white-space: nowrap;
td, th { td, th {
padding: 10px 0; padding: 5px;
text-align: left; text-align: left;
} }
td { td {
@ -65,11 +65,6 @@
font-size: 14px; font-size: 14px;
} }
} }
tr:last-child {
td, th {
padding-bottom: 15px !important;
}
}
.Dropdown { .Dropdown {
display: block; display: block;
@ -79,12 +74,15 @@
text-align: left; text-align: left;
float: none; float: none;
} }
.Dropdown-menu {
margin: 0;
}
} }
.Button { .Button {
text-decoration: none; text-decoration: none;
.Badge { .Badge {
margin: -3px 0; margin: -3px 2px -3px 0;
vertical-align: 0; vertical-align: 0;
} }
} }
@ -113,15 +111,17 @@
} }
.PermissionGrid-section { .PermissionGrid-section {
td, th { td, th {
padding-top: 15px; padding-top: 20px;
border-top: 1px solid @control-bg;
} }
} }
.PermissionGrid-child { .PermissionGrid-child {
td, th { td, th {
padding: 5px 0; position: relative;
} }
th { th {
font-weight: normal; font-weight: normal;
} }
&:hover {
background: lighten(@control-bg, 3%);
}
} }