diff --git a/js/admin/dist/app.js b/js/admin/dist/app.js
index 3f666d9e4..3eff28f7d 100644
--- a/js/admin/dist/app.js
+++ b/js/admin/dist/app.js
@@ -16609,6 +16609,22 @@ System.register('flarum/app', ['flarum/App', 'flarum/initializers/store', 'flaru
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);
}
};
@@ -18080,13 +18096,18 @@ System.register('flarum/components/Dropdown', ['flarum/Component', 'flarum/helpe
}
babelHelpers.createClass(Dropdown, [{
+ key: 'init',
+ value: function init() {
+ this.showing = false;
+ }
+ }, {
key: 'view',
value: function view() {
var items = this.props.children ? listItems(this.props.children) : [];
return m(
'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.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
// above the toggle button instead of below it.
this.$().on('shown.bs.dropdown', function () {
+ _this2.showing = true;
+
+ if (_this2.props.onshow) {
+ _this2.props.onshow();
+ }
+
+ m.redraw();
+
var $menu = _this2.$('.Dropdown-menu');
var isRight = $menu.hasClass('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--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 () {
+ _this2.showing = false;
+
if (_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';
-System.register('flarum/components/PermissionDropdown', ['flarum/components/Dropdown', 'flarum/components/Button', 'flarum/components/Separator', 'flarum/models/Group', 'flarum/components/GroupBadge'], function (_export, _context) {
- var Dropdown, Button, Separator, Group, GroupBadge, PermissionDropdown;
+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, Badge, GroupBadge, PermissionDropdown;
function badgeForId(id) {
@@ -19523,6 +19551,30 @@ System.register('flarum/components/PermissionDropdown', ['flarum/components/Drop
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 {
setters: [function (_flarumComponentsDropdown) {
Dropdown = _flarumComponentsDropdown.default;
@@ -19532,6 +19584,8 @@ System.register('flarum/components/PermissionDropdown', ['flarum/components/Drop
Separator = _flarumComponentsSeparator.default;
}, function (_flarumModelsGroup) {
Group = _flarumModelsGroup.default;
+ }, function (_flarumComponentsBadge) {
+ Badge = _flarumComponentsBadge.default;
}, function (_flarumComponentsGroupBadge) {
GroupBadge = _flarumComponentsGroupBadge.default;
}],
@@ -19552,56 +19606,64 @@ System.register('flarum/components/PermissionDropdown', ['flarum/components/Drop
this.props.children = [];
var groupIds = app.data.permissions[this.props.permission] || [];
+
+ groupIds = filterByRequiredPermissions(groupIds, this.props.permission);
+
var everyone = groupIds.indexOf(Group.GUEST_ID) !== -1;
var members = groupIds.indexOf(Group.MEMBER_ID) !== -1;
var adminGroup = app.store.getById('groups', Group.ADMINISTRATOR_ID);
if (everyone) {
- this.props.label = app.translator.trans('core.admin.permissions_controls.everyone_button');
+ this.props.label = Badge.component({ icon: 'globe' });
} else if (members) {
- this.props.label = app.translator.trans('core.admin.permissions_controls.members_button');
+ this.props.label = Badge.component({ icon: 'user' });
} else {
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({
- children: app.translator.trans('core.admin.permissions_controls.everyone_button'),
- icon: everyone ? 'check' : true,
+ children: [Badge.component({ icon: 'user' }), ' ', app.translator.trans('core.admin.permissions_controls.members_button')],
+ icon: members ? 'check' : true,
onclick: function onclick() {
- return _this2.save([Group.GUEST_ID]);
- }
- }));
- }
-
- this.props.children.push(Button.component({
- children: app.translator.trans('core.admin.permissions_controls.members_button'),
- 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,
+ return _this2.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: function onclick(e) {
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);
}
@@ -19638,6 +19700,11 @@ System.register('flarum/components/PermissionDropdown', ['flarum/components/Drop
this.save(groupIds);
}
+ }, {
+ key: 'isGroupDisabled',
+ value: function isGroupDisabled(id) {
+ return filterByRequiredPermissions([id], this.props.permission).indexOf(id) === -1;
+ }
}], [{
key: 'initProps',
value: function initProps(props) {
@@ -19749,7 +19816,7 @@ System.register('flarum/components/PermissionGrid', ['flarum/Component', 'flarum
m(
'th',
null,
- child.icon ? icon(child.icon) : '',
+ icon(child.icon),
child.label
),
permissionCells(child),
@@ -19871,10 +19938,10 @@ System.register('flarum/components/PermissionGrid', ['flarum/Component', 'flarum
value: function moderateItems() {
var items = new ItemList();
- items.add('viewPostIps', {
+ items.add('viewIpsPosts', {
icon: 'bullseye',
label: app.translator.trans('core.admin.permissions.view_post_ips_label'),
- permission: 'discussion.viewPostIps'
+ permission: 'discussion.viewIpsPosts'
}, 110);
items.add('renameDiscussions', {
@@ -23100,11 +23167,13 @@ System.register('flarum/utils/string', [], function (_export, _context) {
_export('slug', slug);
function getPlainContent(string) {
- var dom = $('
').html(string.replace(/(<\/p>|
)/g, '$1 '));
+ var html = string.replace(/(<\/p>|
)/g, '$1 ').replace(/
]*>/ig, ' ');
+
+ var dom = $('').html(html);
dom.find(getPlainContent.removeSelectors.join(',')).remove();
- return dom.text();
+ return dom.text().replace(/\s+/g, ' ').trim();
}
/**
diff --git a/js/admin/src/app.js b/js/admin/src/app.js
index 62ace9270..46a51aef5 100644
--- a/js/admin/src/app.js
+++ b/js/admin/src/app.js
@@ -14,4 +14,20 @@ app.initializers.add('boot', boot, -100);
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;
diff --git a/js/admin/src/components/PermissionDropdown.js b/js/admin/src/components/PermissionDropdown.js
index d61674392..425440ddf 100644
--- a/js/admin/src/components/PermissionDropdown.js
+++ b/js/admin/src/components/PermissionDropdown.js
@@ -2,6 +2,7 @@ import Dropdown from 'flarum/components/Dropdown';
import Button from 'flarum/components/Button';
import Separator from 'flarum/components/Separator';
import Group from 'flarum/models/Group';
+import Badge from 'flarum/components/Badge';
import GroupBadge from 'flarum/components/GroupBadge';
function badgeForId(id) {
@@ -10,6 +11,27 @@ function badgeForId(id) {
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 {
static initProps(props) {
super.initProps(props);
@@ -21,15 +43,18 @@ export default class PermissionDropdown extends Dropdown {
view() {
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 members = groupIds.indexOf(Group.MEMBER_ID) !== -1;
const adminGroup = app.store.getById('groups', Group.ADMINISTRATOR_ID);
if (everyone) {
- this.props.label = app.translator.trans('core.admin.permissions_controls.everyone_button');
+ this.props.label = Badge.component({icon: 'globe'});
} else if (members) {
- this.props.label = app.translator.trans('core.admin.permissions_controls.members_button');
+ this.props.label = Badge.component({icon: 'user'});
} else {
this.props.label = [
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(
Button.component({
- children: app.translator.trans('core.admin.permissions_controls.everyone_button'),
- icon: everyone ? 'check' : true,
- onclick: () => this.save([Group.GUEST_ID])
+ children: [Badge.component({icon: 'user'}), ' ', app.translator.trans('core.admin.permissions_controls.members_button')],
+ icon: members ? 'check' : true,
+ 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();
}
@@ -112,4 +142,8 @@ export default class PermissionDropdown extends Dropdown {
this.save(groupIds);
}
+
+ isGroupDisabled(id) {
+ return filterByRequiredPermissions([id], this.props.permission).indexOf(id) === -1;
+ }
}
diff --git a/js/admin/src/components/PermissionGrid.js b/js/admin/src/components/PermissionGrid.js
index acb3ca39e..7d452e799 100644
--- a/js/admin/src/components/PermissionGrid.js
+++ b/js/admin/src/components/PermissionGrid.js
@@ -44,7 +44,7 @@ export default class PermissionGrid extends Component {
{section.children.map(child => (
- {child.icon ? icon(child.icon) : ''}{child.label} |
+ {icon(child.icon)}{child.label} |
{permissionCells(child)}
|
diff --git a/js/forum/dist/app.js b/js/forum/dist/app.js
index 9fd2bd127..d73c4a327 100644
--- a/js/forum/dist/app.js
+++ b/js/forum/dist/app.js
@@ -21457,13 +21457,18 @@ System.register('flarum/components/Dropdown', ['flarum/Component', 'flarum/helpe
}
babelHelpers.createClass(Dropdown, [{
+ key: 'init',
+ value: function init() {
+ this.showing = false;
+ }
+ }, {
key: 'view',
value: function view() {
var items = this.props.children ? listItems(this.props.children) : [];
return m(
'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.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
// above the toggle button instead of below it.
this.$().on('shown.bs.dropdown', function () {
+ _this2.showing = true;
+
+ if (_this2.props.onshow) {
+ _this2.props.onshow();
+ }
+
+ m.redraw();
+
var $menu = _this2.$('.Dropdown-menu');
var isRight = $menu.hasClass('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--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 () {
+ _this2.showing = false;
+
if (_this2.props.onhide) {
_this2.props.onhide();
- m.redraw();
}
+
+ m.redraw();
});
}
}, {
diff --git a/js/lib/components/Dropdown.js b/js/lib/components/Dropdown.js
index 749f00141..8093ba506 100644
--- a/js/lib/components/Dropdown.js
+++ b/js/lib/components/Dropdown.js
@@ -29,11 +29,15 @@ export default class Dropdown extends Component {
props.caretIcon = typeof props.caretIcon !== 'undefined' ? props.caretIcon : 'caret-down';
}
+ init() {
+ this.showing = false;
+ }
+
view() {
const items = this.props.children ? listItems(this.props.children) : [];
return (
-
+
{this.getButton()}
{this.getMenu(items)}
@@ -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
// above the toggle button instead of below it.
this.$().on('shown.bs.dropdown', () => {
+ this.showing = true;
+
+ if (this.props.onshow) {
+ this.props.onshow();
+ }
+
+ m.redraw();
+
const $menu = this.$('.Dropdown-menu');
const isRight = $menu.hasClass('Dropdown-menu--right');
+
$menu.removeClass('Dropdown-menu--top Dropdown-menu--right');
$menu.toggleClass(
@@ -60,18 +73,16 @@ export default class Dropdown extends Component {
'Dropdown-menu--right',
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.showing = false;
+
if (this.props.onhide) {
this.props.onhide();
- m.redraw();
}
+
+ m.redraw();
});
}
diff --git a/less/admin/PermissionsPage.less b/less/admin/PermissionsPage.less
index a2bc37263..c180b2afe 100644
--- a/less/admin/PermissionsPage.less
+++ b/less/admin/PermissionsPage.less
@@ -39,7 +39,7 @@
white-space: nowrap;
td, th {
- padding: 10px 0;
+ padding: 5px;
text-align: left;
}
td {
@@ -65,11 +65,6 @@
font-size: 14px;
}
}
- tr:last-child {
- td, th {
- padding-bottom: 15px !important;
- }
- }
.Dropdown {
display: block;
@@ -79,12 +74,15 @@
text-align: left;
float: none;
}
+ .Dropdown-menu {
+ margin: 0;
+ }
}
.Button {
text-decoration: none;
.Badge {
- margin: -3px 0;
+ margin: -3px 2px -3px 0;
vertical-align: 0;
}
}
@@ -113,15 +111,17 @@
}
.PermissionGrid-section {
td, th {
- padding-top: 15px;
- border-top: 1px solid @control-bg;
+ padding-top: 20px;
}
}
.PermissionGrid-child {
td, th {
- padding: 5px 0;
+ position: relative;
}
th {
font-weight: normal;
}
+ &:hover {
+ background: lighten(@control-bg, 3%);
+ }
}