mirror of
https://github.com/flarum/framework.git
synced 2024-12-12 06:03:39 +08:00
Finish admin permissions page and clean up everything
This commit is contained in:
parent
973896c7ab
commit
50215cedfc
25
framework/core/js/admin/src/components/ConfigDropdown.js
Normal file
25
framework/core/js/admin/src/components/ConfigDropdown.js
Normal 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
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
105
framework/core/js/admin/src/components/EditGroupModal.js
Normal file
105
framework/core/js/admin/src/components/EditGroupModal.js
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
115
framework/core/js/admin/src/components/PermissionDropdown.js
Normal file
115
framework/core/js/admin/src/components/PermissionDropdown.js
Normal 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);
|
||||
}
|
||||
}
|
217
framework/core/js/admin/src/components/PermissionGrid.js
Normal file
217
framework/core/js/admin/src/components/PermissionGrid.js
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,9 +37,9 @@ export default class ChangeEmailModal extends Modal {
|
|||
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div class="Form Form--centered">
|
||||
<p class="helpText">{m.trust(app.trans('core.confirmation_email_sent', {email: <strong>{this.email()}</strong>}))}</p>
|
||||
<div class="Form-group">
|
||||
<div className="Form Form--centered">
|
||||
<p className="helpText">{m.trust(app.trans('core.confirmation_email_sent', {email: <strong>{this.email()}</strong>}))}</p>
|
||||
<div className="Form-group">
|
||||
<a href={'http://' + emailProviderName} className="Button Button--primary Button--block">
|
||||
{app.trans('core.go_to', {location: emailProviderName})}
|
||||
</a>
|
||||
|
@ -51,15 +51,15 @@ export default class ChangeEmailModal extends Modal {
|
|||
|
||||
return (
|
||||
<div className="Modal-body">
|
||||
<div class="Form Form--centered">
|
||||
<div class="Form-group">
|
||||
<div className="Form Form--centered">
|
||||
<div className="Form-group">
|
||||
<input type="email" name="email" className="FormControl"
|
||||
placeholder={app.session.user.email()}
|
||||
value={this.email()}
|
||||
onchange={m.withAttr('value', this.email)}
|
||||
disabled={this.loading}/>
|
||||
</div>
|
||||
<div class="Form-group">
|
||||
<div className="Form-group">
|
||||
<button type="submit" className="Button Button--primary Button--block" disabled={this.loading}>
|
||||
{app.trans('core.save_changes')}
|
||||
</button>
|
||||
|
|
|
@ -49,7 +49,8 @@ export default class Post extends Component {
|
|||
children: controls,
|
||||
className: 'Post-controls',
|
||||
buttonClassName: 'Button Button--icon Button--flat',
|
||||
menuClassName: 'Dropdown-menu--right'
|
||||
menuClassName: 'Dropdown-menu--right',
|
||||
icon: 'ellipsis-v'
|
||||
}) : ''}
|
||||
|
||||
{this.content()}
|
||||
|
|
|
@ -62,7 +62,7 @@ export default class SessionDropdown extends Dropdown {
|
|||
50
|
||||
);
|
||||
|
||||
if (user.groups().some(group => Number(group.id()) === Group.ADMINISTRATOR_ID)) {
|
||||
if (user.groups().some(group => group.id() === Group.ADMINISTRATOR_ID)) {
|
||||
items.add('administration',
|
||||
LinkButton.component({
|
||||
icon: 'wrench',
|
||||
|
|
|
@ -189,9 +189,10 @@ export default class Model {
|
|||
method: 'DELETE',
|
||||
url: app.forum.attribute('apiUrl') + this.apiEndpoint(),
|
||||
data
|
||||
}).then(
|
||||
() => this.exists = false
|
||||
);
|
||||
}).then(() => {
|
||||
this.exists = false;
|
||||
this.store.remove(this);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -214,7 +215,7 @@ export default class Model {
|
|||
*/
|
||||
static attribute(name, transform) {
|
||||
return function() {
|
||||
const value = this.data.attributes[name];
|
||||
const value = this.data.attributes && this.data.attributes[name];
|
||||
|
||||
return transform ? transform(value) : value;
|
||||
};
|
||||
|
|
|
@ -139,6 +139,15 @@ export default class Store {
|
|||
return records ? Object.keys(records).map(id => records[id]) : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the given model from the store.
|
||||
*
|
||||
* @param {Model} model
|
||||
*/
|
||||
remove(model) {
|
||||
delete this.data[model.data.type][model.id()];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new record of the given type.
|
||||
*
|
||||
|
|
|
@ -20,17 +20,17 @@ export default class Badge extends Component {
|
|||
const type = extract(attrs, 'type');
|
||||
const iconName = extract(attrs, 'icon');
|
||||
|
||||
attrs.className = 'Badge Badge--' + type + ' ' + (attrs.className || '');
|
||||
attrs.title = extract(attrs, 'label');
|
||||
attrs.className = 'Badge ' + (type ? 'Badge--' + type : '') + ' ' + (attrs.className || '');
|
||||
attrs.title = extract(attrs, 'label') || '';
|
||||
|
||||
// Give the badge a unique key so that when badges are displayed together,
|
||||
// and then one is added/removed, Mithril will correctly redraw the series
|
||||
// of badges.
|
||||
attrs.key = attrs.className;
|
||||
attrs.key = attrs.type;
|
||||
|
||||
return (
|
||||
<span {...attrs}>
|
||||
{iconName ? icon(iconName, {className: 'Badge-icon'}) : ''}
|
||||
{iconName ? icon(iconName, {className: 'Badge-icon'}) : m.trust(' ')}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -49,7 +49,7 @@ export default class Button extends Component {
|
|||
const iconName = this.props.icon;
|
||||
|
||||
return [
|
||||
iconName ? icon(iconName, {className: 'Button-icon'}) : '', ' ',
|
||||
iconName && iconName !== true ? icon(iconName, {className: 'Button-icon'}) : '',
|
||||
this.props.children ? <span className="Button-label">{this.props.children}</span> : '',
|
||||
this.props.loading ? LoadingIndicator.component({size: 'tiny', className: 'LoadingIndicator--inline'}) : ''
|
||||
];
|
||||
|
|
|
@ -10,9 +10,10 @@ import listItems from 'flarum/helpers/listItems';
|
|||
*
|
||||
* - `buttonClassName` A class name to apply to the dropdown toggle button.
|
||||
* - `menuClassName` A class name to apply to the dropdown menu.
|
||||
* - `icon` The name of an icon to show in the dropdown toggle button. Defaults
|
||||
* to 'ellipsis-v'.
|
||||
* - `icon` The name of an icon to show in the dropdown toggle button.
|
||||
* - `caretIcon` The name of an icon to show on the right of the button.
|
||||
* - `label` The label of the dropdown toggle button. Defaults to 'Controls'.
|
||||
* - `onhide`
|
||||
*
|
||||
* The children will be displayed as a list inside of the dropdown menu.
|
||||
*/
|
||||
|
@ -23,8 +24,8 @@ export default class Dropdown extends Component {
|
|||
props.className = props.className || '';
|
||||
props.buttonClassName = props.buttonClassName || '';
|
||||
props.contentClassName = props.contentClassName || '';
|
||||
props.icon = props.icon || 'ellipsis-v';
|
||||
props.label = props.label || app.trans('core.controls');
|
||||
props.caretIcon = typeof props.caretIcon !== 'undefined' ? props.caretIcon : 'caret-down';
|
||||
}
|
||||
|
||||
view() {
|
||||
|
@ -40,6 +41,29 @@ export default class Dropdown extends Component {
|
|||
);
|
||||
}
|
||||
|
||||
config(isInitialized) {
|
||||
if (isInitialized) return;
|
||||
|
||||
// When opening the dropdown menu, work out if the menu goes beyond the
|
||||
// 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', () => {
|
||||
const $menu = this.$('.Dropdown-menu').removeClass('Dropdown-menu--top');
|
||||
|
||||
$menu.toggleClass(
|
||||
'Dropdown-menu--top',
|
||||
$menu.offset().top + $menu.height() > $(window).scrollTop() + $(window).height()
|
||||
);
|
||||
});
|
||||
|
||||
this.$().on('hide.bs.dropdown', () => {
|
||||
if (this.props.onhide) {
|
||||
this.props.onhide();
|
||||
m.redraw();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the template for the button.
|
||||
*
|
||||
|
@ -65,9 +89,9 @@ export default class Dropdown extends Component {
|
|||
*/
|
||||
getButtonContent() {
|
||||
return [
|
||||
icon(this.props.icon, {className: 'Button-icon'}),
|
||||
this.props.icon ? icon(this.props.icon, {className: 'Button-icon'}) : '',
|
||||
<span className="Button-label">{this.props.label}</span>, ' ',
|
||||
icon('caret-down', {className: 'Button-caret'})
|
||||
this.props.caretIcon ? icon(this.props.caretIcon, {className: 'Button-caret'}) : ''
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
16
framework/core/js/lib/components/GroupBadge.js
Normal file
16
framework/core/js/lib/components/GroupBadge.js
Normal file
|
@ -0,0 +1,16 @@
|
|||
import Badge from 'flarum/components/Badge';
|
||||
|
||||
export default class GroupBadge extends Badge {
|
||||
static initProps(props) {
|
||||
super.initProps(props);
|
||||
|
||||
if (props.group) {
|
||||
props.icon = props.group.icon();
|
||||
props.style = {backgroundColor: props.group.color()};
|
||||
props.label = typeof props.label === 'undefined' ? props.group.nameSingular() : props.label;
|
||||
props.type = 'group--' + props.group.nameSingular();
|
||||
|
||||
delete props.group;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,23 +5,29 @@ import icon from 'flarum/helpers/icon';
|
|||
* The `SelectDropdown` component is the same as a `Dropdown`, except the toggle
|
||||
* button's label is set as the label of the first child which has a truthy
|
||||
* `active` prop.
|
||||
*
|
||||
* ### Props
|
||||
*
|
||||
* - `caretIcon`
|
||||
* - `defaultLabel`
|
||||
*/
|
||||
export default class SelectDropdown extends Dropdown {
|
||||
static initProps(props) {
|
||||
super.initProps(props);
|
||||
|
||||
props.className += ' Dropdown--select';
|
||||
props.caretIcon = props.caretIcon || 'sort';
|
||||
}
|
||||
|
||||
getButtonContent() {
|
||||
const activeChild = this.props.children.filter(child => child.props.active)[0];
|
||||
let label = activeChild && activeChild.props.children;
|
||||
let label = activeChild && activeChild.props.children || this.props.defaultLabel;
|
||||
|
||||
if (label instanceof Array) label = label[0];
|
||||
|
||||
return [
|
||||
<span className="Button-label">{label}</span>, ' ',
|
||||
icon('sort', {className: 'Button-caret'})
|
||||
icon(this.props.caretIcon, {className: 'Button-caret'})
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@ class Group extends mixin(Model, {
|
|||
icon: Model.attribute('icon')
|
||||
}) {}
|
||||
|
||||
Group.ADMINISTRATOR_ID = 1;
|
||||
Group.GUEST_ID = 2;
|
||||
Group.MEMBER_ID = 3;
|
||||
Group.ADMINISTRATOR_ID = '1';
|
||||
Group.GUEST_ID = '2';
|
||||
Group.MEMBER_ID = '3';
|
||||
|
||||
export default Group;
|
||||
|
|
|
@ -70,9 +70,11 @@
|
|||
}
|
||||
.container {
|
||||
width: 100%;
|
||||
padding: 0 30px;
|
||||
margin: 0;
|
||||
|
||||
.App-content & {
|
||||
padding: 0 30px;
|
||||
}
|
||||
.App-content > & {
|
||||
padding: 0;
|
||||
}
|
||||
|
|
23
framework/core/less/admin/EditGroupModal.less
Normal file
23
framework/core/less/admin/EditGroupModal.less
Normal file
|
@ -0,0 +1,23 @@
|
|||
.EditGroupModal {
|
||||
.Form-group:not(:last-child) {
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.Badge {
|
||||
margin-right: 5px;
|
||||
vertical-align: 2px;
|
||||
}
|
||||
}
|
||||
.EditGroupModal-name-input {
|
||||
:first-child {
|
||||
margin-bottom: 1px;
|
||||
border-bottom-right-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
:last-child {
|
||||
border-top-right-radius: 0;
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
}
|
||||
.EditGroupModal-delete {
|
||||
float: right;
|
||||
}
|
|
@ -8,28 +8,39 @@
|
|||
text-align: center;
|
||||
color: @text-color;
|
||||
font-weight: bold;
|
||||
padding-left: 10px;
|
||||
padding-right: 10px;
|
||||
}
|
||||
.Group-name {
|
||||
display: block;
|
||||
margin-top: 5px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.Group-icon {
|
||||
font-size: 14px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
.Group--add {
|
||||
border: 1px dashed @muted-color;
|
||||
color: @muted-color;
|
||||
width: auto;
|
||||
margin-left: 10px;
|
||||
font-weight: normal;
|
||||
|
||||
.Group-icon {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.PermissionsPage-permissions {
|
||||
padding: 30px 0;
|
||||
padding: 30px 0 200px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.PermissionGrid {
|
||||
white-space: nowrap;
|
||||
|
||||
td, th {
|
||||
padding: 10px 0;
|
||||
text-align: left;
|
||||
|
@ -42,7 +53,11 @@
|
|||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
color: @muted-color;
|
||||
width: 140px;
|
||||
min-width: 140px;
|
||||
|
||||
&:not(:hover) .PermissionGrid-removeScope {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
tbody {
|
||||
th {
|
||||
|
@ -62,12 +77,28 @@
|
|||
}
|
||||
.Button {
|
||||
text-decoration: none;
|
||||
}
|
||||
td:not(:hover) {
|
||||
.Select-caret, .GroupsButton-caret {
|
||||
display: none;
|
||||
|
||||
.Badge {
|
||||
margin: -3px 0;
|
||||
vertical-align: 0;
|
||||
}
|
||||
}
|
||||
td:not(:hover) .Select-caret,
|
||||
td:not(:hover) .Dropdown:not(.open) .Button-caret {
|
||||
display: none;
|
||||
}
|
||||
.open .Dropdown-toggle {
|
||||
.box-shadow(none);
|
||||
}
|
||||
}
|
||||
}
|
||||
.PermissionGrid-removeScope {
|
||||
margin: -1px 0;
|
||||
}
|
||||
.PermissionDropdown {
|
||||
.Badge {
|
||||
margin: -3px 3px -3px 0;
|
||||
vertical-align: 1px;
|
||||
}
|
||||
}
|
||||
.PermissionGrid-section {
|
||||
|
|
|
@ -4,3 +4,4 @@
|
|||
@import "DashboardPage.less";
|
||||
@import "BasicsPage.less";
|
||||
@import "PermissionsPage.less";
|
||||
@import "EditGroupModal.less";
|
||||
|
|
|
@ -15,6 +15,13 @@
|
|||
color: @alert-error-color;
|
||||
}
|
||||
}
|
||||
.Alert--success {
|
||||
background: @alert-success-bg;
|
||||
|
||||
&, a, a:hover, button, button:hover {
|
||||
color: @alert-success-color;
|
||||
}
|
||||
}
|
||||
.Alert-controls {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
|
@ -39,6 +46,7 @@
|
|||
|
||||
> .Button {
|
||||
margin: -10px;
|
||||
vertical-align: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
.Badge {
|
||||
.Badge--size(23px);
|
||||
.Badge--size(24px);
|
||||
border: 1px solid @body-bg;
|
||||
background: @muted-color;
|
||||
color: #fff;
|
||||
|
@ -20,7 +20,7 @@
|
|||
line-height: @size - 3px;
|
||||
|
||||
&, .Badge-icon {
|
||||
font-size: 0.56 * @size;
|
||||
font-size: 0.58 * @size;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -157,12 +157,14 @@
|
|||
background: transparent !important;
|
||||
padding: 0;
|
||||
color: inherit !important;
|
||||
line-height: inherit;
|
||||
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
&:active,
|
||||
&.active {
|
||||
&.active,
|
||||
.open > &.Dropdown-toggle {
|
||||
.box-shadow(none);
|
||||
}
|
||||
}
|
||||
|
@ -204,6 +206,7 @@
|
|||
.Button-icon {
|
||||
font-size: 16px;
|
||||
vertical-align: -1px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
.SessionDropdown .Dropdown-toggle {
|
||||
|
@ -214,6 +217,9 @@
|
|||
.Avatar--size(24px);
|
||||
}
|
||||
}
|
||||
.Button-icon {
|
||||
margin-right: 3px;
|
||||
}
|
||||
.Button-icon,
|
||||
.Button-caret {
|
||||
font-size: 14px;
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
display: none;
|
||||
min-width: 160px;
|
||||
padding: 8px 0;
|
||||
margin: 7px 0 0;
|
||||
margin: 7px 0;
|
||||
background: @body-bg;
|
||||
border-radius: @border-radius;
|
||||
.box-shadow(0 2px 6px @shadow-color);
|
||||
|
@ -42,8 +42,10 @@
|
|||
&.hasIcon {
|
||||
padding-left: 40px;
|
||||
}
|
||||
&:hover, &:focus {
|
||||
&:hover {
|
||||
background: @control-bg;
|
||||
}
|
||||
&:focus {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
|
@ -52,6 +54,11 @@
|
|||
margin-left: -25px;
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
opacity: 0.5;
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
&.active {
|
||||
> a, > button {
|
||||
|
@ -60,6 +67,10 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
.Dropdown-menu--top {
|
||||
top: auto;
|
||||
bottom: 100%;
|
||||
}
|
||||
.Dropdown-menu--right {
|
||||
left: auto;
|
||||
right: 0;
|
||||
|
|
|
@ -13,3 +13,11 @@
|
|||
.Form-group {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.Form-group label {
|
||||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
color: @text-color;
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -101,11 +101,13 @@
|
|||
color: @text-color;
|
||||
}
|
||||
|
||||
.helpText {
|
||||
font-size: 14px;
|
||||
line-height: 1.5em;
|
||||
margin-bottom: 25px;
|
||||
text-align: left;
|
||||
.Form--centered {
|
||||
.helpText {
|
||||
font-size: 14px;
|
||||
line-height: 1.5em;
|
||||
margin-bottom: 25px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
> :last-child {
|
||||
|
|
|
@ -88,6 +88,7 @@ legend {
|
|||
font-size: 14px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 10px;
|
||||
color: @text-color;
|
||||
}
|
||||
input[type="search"] {
|
||||
-webkit-appearance: none;
|
||||
|
|
|
@ -68,6 +68,9 @@
|
|||
@alert-error-bg: #d83e3e;
|
||||
@alert-error-color: #fff;
|
||||
|
||||
@alert-success-bg: #B4F1AF;
|
||||
@alert-success-color: #33722D;
|
||||
|
||||
.define-header(@config-colored-header);
|
||||
.define-header(false) {
|
||||
@header-bg: @body-bg;
|
||||
|
|
42
framework/core/src/Admin/Actions/UpdateConfigAction.php
Normal file
42
framework/core/src/Admin/Actions/UpdateConfigAction.php
Normal file
|
@ -0,0 +1,42 @@
|
|||
<?php namespace Flarum\Admin\Actions;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Flarum\Core\Settings\SettingsRepository;
|
||||
use Flarum\Support\Action;
|
||||
use Flarum\Core\Groups\Permission;
|
||||
use Exception;
|
||||
|
||||
class UpdateConfigAction extends Action
|
||||
{
|
||||
/**
|
||||
* @var SettingsRepository
|
||||
*/
|
||||
protected $settings;
|
||||
|
||||
/**
|
||||
* @param SettingsRepository $settings
|
||||
*/
|
||||
public function __construct(SettingsRepository $settings)
|
||||
{
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handle(Request $request, array $routeParams = [])
|
||||
{
|
||||
$config = array_get($request->getAttributes(), 'config', []);
|
||||
|
||||
// TODO: throw HTTP status 400 or 422
|
||||
if (! is_array($config)) {
|
||||
throw new Exception;
|
||||
}
|
||||
|
||||
foreach ($config as $k => $v) {
|
||||
$this->settings->set($k, $v);
|
||||
}
|
||||
|
||||
return $this->success();
|
||||
}
|
||||
}
|
29
framework/core/src/Admin/Actions/UpdatePermissionAction.php
Normal file
29
framework/core/src/Admin/Actions/UpdatePermissionAction.php
Normal file
|
@ -0,0 +1,29 @@
|
|||
<?php namespace Flarum\Admin\Actions;
|
||||
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Flarum\Support\Action;
|
||||
use Flarum\Core\Groups\Permission;
|
||||
|
||||
class UpdatePermissionAction extends Action
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function handle(Request $request, array $routeParams = [])
|
||||
{
|
||||
$input = $request->getAttributes();
|
||||
$permission = array_get($input, 'permission');
|
||||
$groupIds = array_get($input, 'groupIds');
|
||||
|
||||
Permission::where('permission', $permission)->delete();
|
||||
|
||||
Permission::insert(array_map(function ($groupId) use ($permission) {
|
||||
return [
|
||||
'permission' => $permission,
|
||||
'group_id' => $groupId
|
||||
];
|
||||
}, $groupIds));
|
||||
|
||||
return $this->success();
|
||||
}
|
||||
}
|
|
@ -50,6 +50,18 @@ class AdminServiceProvider extends ServiceProvider
|
|||
'flarum.admin.index',
|
||||
$this->action('Flarum\Admin\Actions\ClientAction')
|
||||
);
|
||||
|
||||
$routes->post(
|
||||
'/config',
|
||||
'flarum.admin.updateConfig',
|
||||
$this->action('Flarum\Admin\Actions\UpdateConfigAction')
|
||||
);
|
||||
|
||||
$routes->post(
|
||||
'/permission',
|
||||
'flarum.admin.updatePermission',
|
||||
$this->action('Flarum\Admin\Actions\UpdatePermissionAction')
|
||||
);
|
||||
}
|
||||
|
||||
protected function action($class)
|
||||
|
|
|
@ -32,6 +32,7 @@ class ForumSerializer extends Serializer
|
|||
];
|
||||
|
||||
if ($this->actor->isAdmin()) {
|
||||
$attributes['adminUrl'] = Core::config('admin_url');
|
||||
}
|
||||
|
||||
return $attributes;
|
||||
|
|
|
@ -29,6 +29,10 @@ class DatabaseSettingsRepository implements SettingsRepository
|
|||
|
||||
public function set($key, $value)
|
||||
{
|
||||
$this->database->table('config')->where('key', $key)->update(['value' => $value]);
|
||||
$query = $this->database->table('config')->where('key', $key);
|
||||
|
||||
$method = $query->exists() ? 'update' : 'insert';
|
||||
|
||||
$query->$method(compact('key', 'value'));
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user