Finish admin permissions page and clean up everything

This commit is contained in:
Toby Zerner
2015-07-31 20:16:47 +09:30
parent 5706c71c86
commit fde7afd3e2
33 changed files with 766 additions and 291 deletions

View File

@ -0,0 +1,25 @@
import SelectDropdown from 'flarum/components/SelectDropdown';
import Button from 'flarum/components/Button';
import saveConfig from 'flarum/utils/saveConfig';
export default class ConfigDropdown extends SelectDropdown {
static initProps(props) {
super.initProps(props);
props.className = 'ConfigDropdown';
props.buttonClassName = 'Button Button--text';
props.caretIcon = 'caret-down';
props.defaultLabel = 'Custom';
props.children = props.options.map(({value, label}) => {
const active = app.config[props.key] === value;
return Button.component({
children: label,
icon: active ? 'check' : true,
onclick: saveConfig.bind(this, {[props.key]: value}),
active
});
});
}
}

View File

@ -0,0 +1,105 @@
import Modal from 'flarum/components/Modal';
import Button from 'flarum/components/Button';
import Badge from 'flarum/components/Badge';
import Group from 'flarum/models/Group';
/**
* The `EditGroupModal` component shows a modal dialog which allows the user
* to create or edit a group.
*/
export default class EditGroupModal extends Modal {
constructor(...args) {
super(...args);
this.group = this.props.group || app.store.createRecord('groups');
this.nameSingular = m.prop(this.group.nameSingular() || '');
this.namePlural = m.prop(this.group.namePlural() || '');
this.icon = m.prop(this.group.icon() || '');
this.color = m.prop(this.group.color() || '');
}
className() {
return 'EditGroupModal Modal--small';
}
title() {
return [
this.color() || this.icon() ? Badge.component({
icon: this.icon(),
style: {backgroundColor: this.color()}
}) : '',
' ',
this.namePlural() || 'Create Group'
];
}
content() {
return (
<div className="Modal-body">
<div className="Form">
<div className="Form-group">
<label>Name</label>
<div className="EditGroupModal-name-input">
<input className="FormControl" placeholder="Singular (e.g. Mod)" value={this.nameSingular()} oninput={m.withAttr('value', this.nameSingular)}/>
<input className="FormControl" placeholder="Plural (e.g. Mods)" value={this.namePlural()} oninput={m.withAttr('value', this.namePlural)}/>
</div>
</div>
<div className="Form-group">
<label>Color</label>
<input className="FormControl" placeholder="#aaaaaa" value={this.color()} oninput={m.withAttr('value', this.color)}/>
</div>
<div className="Form-group">
<label>Icon</label>
<div className="helpText">
Enter the name of any <a href="http://fortawesome.github.io/Font-Awesome/icons/" tabindex="-1">FontAwesome</a> icon class, <em>without</em> the <code>fa-</code> prefix.
</div>
<input className="FormControl" placeholder="bolt" value={this.icon()} oninput={m.withAttr('value', this.icon)}/>
</div>
<div className="Form-group">
{Button.component({
type: 'submit',
className: 'Button Button--primary EditGroupModal-save',
loading: this._loading,
children: 'Save Changes'
})}
{this.group.exists && this.group.id() !== Group.ADMINISTRATOR_ID ? (
<button type="button" className="Button EditGroupModal-delete" onclick={this.delete.bind(this)}>
Delete Group
</button>
) : ''}
</div>
</div>
</div>
);
}
onsubmit(e) {
e.preventDefault();
this._loading = true;
this.group.save({
nameSingular: this.nameSingular(),
namePlural: this.namePlural(),
color: this.color(),
icon: this.icon()
}).then(
() => this.hide(),
() => {
this._loading = false;
m.redraw();
}
);
}
delete() {
if (confirm('Are you sure you want to delete this group? The group members will NOT be deleted.')) {
this.group.delete().then(() => m.redraw());
this.hide();
}
}
}

View File

@ -0,0 +1,115 @@
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 GroupBadge from 'flarum/components/GroupBadge';
function badgeForId(id) {
const group = app.store.getById('groups', id);
return group ? GroupBadge.component({group, label: null}) : '';
}
export default class PermissionDropdown extends Dropdown {
static initProps(props) {
super.initProps(props);
props.className = 'PermissionDropdown';
props.buttonClassName = 'Button Button--text';
}
view() {
this.props.children = [];
const groupIds = app.permissions[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 = 'Everyone';
} else if (members) {
this.props.label = 'Members';
} else {
this.props.label = [
badgeForId(Group.ADMINISTRATOR_ID),
groupIds.map(badgeForId)
];
}
if (this.props.allowGuest) {
this.props.children.push(
Button.component({
children: 'Everyone',
icon: everyone ? 'check' : true,
onclick: () => this.save([Group.GUEST_ID])
})
);
}
this.props.children.push(
Button.component({
children: 'Members',
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 => {
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) => {
e.stopPropagation();
this.toggle(group.id());
}
}))
);
return super.view();
}
save(groupIds) {
const permission = this.props.permission;
app.permissions[permission] = groupIds;
app.request({
method: 'POST',
url: app.forum.attribute('adminUrl') + '/permission',
data: {permission, groupIds}
});
}
toggle(groupId) {
const permission = this.props.permission;
let groupIds = app.permissions[permission] || [];
const index = groupIds.indexOf(groupId);
if (index !== -1) {
groupIds.splice(index, 1);
} else {
groupIds.push(groupId);
groupIds = groupIds.filter(id => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(id) === -1);
}
this.save(groupIds);
}
}

View File

@ -0,0 +1,217 @@
import Component from 'flarum/Component';
import PermissionDropdown from 'flarum/components/PermissionDropdown';
import ConfigDropdown from 'flarum/components/ConfigDropdown';
import Button from 'flarum/components/Button';
import ItemList from 'flarum/utils/ItemList';
export default class PermissionGrid extends Component {
constructor(...args) {
super(...args);
this.permissions = this.permissionItems().toArray();
}
view() {
const scopes = this.scopeItems().toArray();
const permissionCells = permission => {
return scopes.map(scope => (
<td>
{scope.render(permission)}
</td>
));
};
return (
<table className="PermissionGrid">
<thead>
<tr>
<td></td>
{scopes.map(scope => (
<th>
{scope.label}{' '}
{scope.onremove ? Button.component({icon: 'times', className: 'Button Button--text PermissionGrid-removeScope', onclick: scope.onremove}) : ''}
</th>
))}
<th>{this.scopeControlItems().toArray()}</th>
</tr>
</thead>
{this.permissions.map(section => (
<tbody>
<tr className="PermissionGrid-section">
<th>{section.label}</th>
{permissionCells(section)}
<td/>
</tr>
{section.children.map(child => (
<tr className="PermissionGrid-child">
<th>{child.label}</th>
{permissionCells(child)}
<td/>
</tr>
))}
</tbody>
))}
</table>
);
}
permissionItems() {
const items = new ItemList();
items.add('view', {
label: 'View the forum',
children: this.viewItems().toArray()
});
items.add('start', {
label: 'Start discussions',
children: this.startItems().toArray()
});
items.add('reply', {
label: 'Reply to discussions',
children: this.replyItems().toArray()
});
items.add('moderate', {
label: 'Moderate',
children: this.moderateItems().toArray()
});
return items;
}
viewItems() {
const items = new ItemList();
items.add('view', {
label: 'View discussions',
permission: 'forum.view',
allowGuest: true
});
items.add('signUp', {
label: 'Sign up',
setting: () => ConfigDropdown.component({
key: 'allow_sign_up',
options: [
{value: '1', label: 'Open'},
{value: '0', label: 'Closed'}
]
})
});
return items;
}
startItems() {
const items = new ItemList();
items.add('start', {
label: 'Start discussions',
permission: 'forum.startDiscussion'
});
items.add('allowRenaming', {
label: 'Allow renaming',
setting: () => {
const minutes = parseInt(app.config.allow_renaming, 10);
return ConfigDropdown.component({
defaultLabel: minutes ? `For ${minutes} minutes` : 'Indefinitely',
key: 'allow_renaming',
options: [
{value: '-1', label: 'Indefinitely'},
{value: '10', label: 'For 10 minutes'},
{value: 'reply', label: 'Until next reply'}
]
});
}
});
return items;
}
replyItems() {
const items = new ItemList();
items.add('reply', {
label: 'Reply to discussions',
permission: 'discussion.reply'
});
items.add('allowPostEditing', {
label: 'Allow post editing',
setting: () => {
const minutes = parseInt(app.config.allow_post_editing, 10);
return ConfigDropdown.component({
defaultLabel: minutes ? `For ${minutes} minutes` : 'Indefinitely',
key: 'allow_post_editing',
options: [
{value: '-1', label: 'Indefinitely'},
{value: '10', label: 'For 10 minutes'},
{value: 'reply', label: 'Until next reply'}
]
});
}
});
return items;
}
moderateItems() {
const items = new ItemList();
items.add('editPosts', {
label: 'Edit posts',
permission: 'discussion.editPosts'
});
items.add('deletePosts', {
label: 'Delete posts',
permission: 'discussion.deletePosts'
});
items.add('renameDiscussions', {
label: 'Rename discussions',
permission: 'discussion.rename'
});
items.add('deleteDiscussions', {
label: 'Delete discussions',
permission: 'discussion.delete'
});
items.add('suspendUsers', {
label: 'Suspend users',
permission: 'user.suspend'
});
return items;
}
scopeItems() {
const items = new ItemList();
items.add('global', {
label: 'Global',
render: item => {
if (item.setting) {
return item.setting();
} else if (item.permission) {
return PermissionDropdown.component(Object.assign({}, item));
}
return '';
}
});
return items;
}
scopeControlItems() {
return new ItemList();
}
}

View File

@ -1,47 +1,29 @@
import Component from 'flarum/Component';
import Badge from 'flarum/components/Badge';
import Select from 'flarum/components/Select';
import Button from 'flarum/components/Button';
import GroupBadge from 'flarum/components/GroupBadge';
import EditGroupModal from 'flarum/components/EditGroupModal';
import Group from 'flarum/models/Group';
import icon from 'flarum/helpers/icon';
import ItemList from 'flarum/utils/ItemList';
import PermissionGrid from 'flarum/components/PermissionGrid';
export default class PermissionsPage extends Component {
constructor(...args) {
super(...args);
this.groups = app.store.all('groups')
.filter(group => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(Number(group.id())) === -1);
this.permissions = this.permissionItems().toArray();
this.scopes = this.scopeItems().toArray();
this.scopeControls = this.scopeControlItems().toArray();
}
view() {
const permissionCells = permission => {
return this.scopes.map(scope => (
<td>
{scope.render(permission)}
</td>
));
};
return (
<div className="PermissionsPage">
<div className="PermissionsPage-groups">
<div className="container">
{this.groups.map(group => (
<button className="Button Group">
{Badge.component({
className: 'Group-icon',
icon: group.icon(),
style: {backgroundColor: group.color()}
})}
<span className="Group-name">{group.namePlural()}</span>
</button>
))}
<button className="Button Group Group--add">
{app.store.all('groups')
.filter(group => [Group.GUEST_ID, Group.MEMBER_ID].indexOf(group.id()) === -1)
.map(group => (
<button className="Button Group" onclick={() => app.modal.show(new EditGroupModal({group}))}>
{GroupBadge.component({
group,
className: 'Group-icon',
label: null
})}
<span className="Group-name">{group.namePlural()}</span>
</button>
))}
<button className="Button Group Group--add" onclick={() => app.modal.show(new EditGroupModal())}>
{icon('plus', {className: 'Group-icon'})}
<span className="Group-name">New Group</span>
</button>
@ -50,220 +32,10 @@ export default class PermissionsPage extends Component {
<div className="PermissionsPage-permissions">
<div className="container">
<table className="PermissionGrid">
<thead>
<tr>
<td></td>
{this.scopes.map(scope => <th>{scope.label}</th>)}
<th>{this.scopeControls}</th>
</tr>
</thead>
{this.permissions.map(section => (
<tbody>
<tr className="PermissionGrid-section">
<th>{section.label}</th>
{permissionCells(section)}
<td/>
</tr>
{section.children.map(child => (
<tr className="PermissionGrid-child">
<th>{child.label}</th>
{permissionCells(child)}
<td/>
</tr>
))}
</tbody>
))}
</table>
{PermissionGrid.component()}
</div>
</div>
</div>
);
}
permissionItems() {
const items = new ItemList();
items.add('view', {
label: 'View the forum',
children: this.viewItems().toArray()
});
items.add('start', {
label: 'Start discussions',
children: this.startItems().toArray()
});
items.add('reply', {
label: 'Reply to discussions',
children: this.replyItems().toArray()
});
items.add('moderate', {
label: 'Moderate',
children: this.moderateItems().toArray()
});
return items;
}
viewItems() {
const items = new ItemList();
items.add('view', {
label: 'View discussions',
permission: 'forum.view',
allowGuest: true
});
items.add('signUp', {
label: 'Sign up',
setting: Select.component({options: ['Open']})
});
return items;
}
startItems() {
const items = new ItemList();
items.add('start', {
label: 'Start discussions',
permission: 'forum.startDiscussion'
});
items.add('allowRenaming', {
label: 'Allow renaming',
setting: Select.component({options: ['Indefinitely']})
});
return items;
}
replyItems() {
const items = new ItemList();
items.add('reply', {
label: 'Reply to discussions',
permission: 'discussion.reply'
});
items.add('allowPostEditing', {
label: 'Allow post editing',
setting: Select.component({options: ['Indefinitely']})
});
return items;
}
moderateItems() {
const items = new ItemList();
items.add('editPosts', {
label: 'Edit posts',
permission: 'discussion.editPosts'
});
items.add('deletePosts', {
label: 'Delete posts',
permission: 'discussion.deletePosts'
});
items.add('renameDiscussions', {
label: 'Rename discussions',
permission: 'discussion.rename'
});
items.add('deleteDiscussions', {
label: 'Delete discussions',
permission: 'discussion.delete'
});
items.add('suspendUsers', {
label: 'Suspend users',
permission: 'user.suspend'
});
return items;
}
scopeItems() {
const items = new ItemList();
const groupBadge = id => {
const group = app.store.getById('groups', id);
return Badge.component({
icon: group.icon(),
style: {backgroundColor: group.color()},
label: group.namePlural()
});
};
const groupBadges = groupIds => {
let content;
if (groupIds.indexOf(String(Group.GUEST_ID)) !== -1) {
content = 'Everyone';
} else if (groupIds.indexOf(String(Group.MEMBER_ID)) !== -1) {
content = 'Members';
} else {
content = [
groupBadge(Group.ADMINISTRATOR_ID),
groupIds.map(groupBadge)
];
}
return (
<button className="Button Button--text">
{content}
{icon('sort', {className: 'GroupsButton-caret'})}
</button>
);
};
items.add('global', {
label: 'Global',
render: permission => {
if (permission.setting) {
return permission.setting;
} else if (permission.permission) {
const groupIds = app.forum.attribute('permissions')[permission.permission] || [];
return groupBadges(groupIds);
}
return '';
}
});
items.add('tag1', {
label: 'Blog',
render: permission => {
if (permission.setting) {
return '';
} else if (permission.permission) {
const groupIds = app.forum.attribute('permissions')[permission.permission] || [];
return groupBadges(groupIds);
}
return '';
}
});
return items;
}
scopeControlItems() {
const items = new ItemList();
items.add('addTag', Button.component({
children: 'Restrict by Tag',
icon: 'plus',
className: 'Button Button--text'
}))
return items;
}
}