Implement basic settings page

This commit is contained in:
Toby Zerner
2015-07-29 21:00:09 +09:30
parent d71d8f59c2
commit f96cac6057
13 changed files with 313 additions and 17 deletions

View File

@ -0,0 +1,137 @@
import Component from 'flarum/Component';
import FieldSet from 'flarum/components/FieldSet';
import Select from 'flarum/components/Select';
import Button from 'flarum/components/Button';
export default class BasicsPage extends Component {
constructor(...args) {
super(...args);
this.loading = false;
this.fields = [
'forum_title',
'forum_description',
'default_locale',
'default_route',
'welcome_title',
'welcome_message'
];
this.values = {};
const config = app.forum.attribute('config');
this.fields.forEach(key => this.values[key] = m.prop(config[key]));
this.localeOptions = {};
const locales = app.forum.attribute('availableLocales');
for (const i in locales) {
this.localeOptions[i] = `${locales[i]} (${i})`;
}
}
view() {
return (
<div className="BasicsPage">
<div className="container">
<form onsubmit={this.onsubmit.bind(this)}>
{FieldSet.component({
label: 'Forum Title',
children: [
<input className="FormControl" value={this.values.forum_title()} oninput={m.withAttr('value', this.values.forum_title)}/>
]
})}
{FieldSet.component({
label: 'Forum Description',
children: [
<div className="helpText">
Enter a short sentence or two that describes your community. This will appear in the meta tag and show up in search engines.
</div>,
<textarea className="FormControl" value={this.values.forum_description()} oninput={m.withAttr('value', this.values.forum_description)}/>
]
})}
{Object.keys(this.localeOptions).length > 1
? FieldSet.component({
label: 'Default Language',
children: [
Select.component({
options: this.localeOptions,
onchange: this.values.default_locale
})
]
})
: ''}
{FieldSet.component({
label: 'Home Page',
className: 'BasicsPage-homePage',
children: [
<div className="helpText">
Choose the page which users will first see when they visit your forum. If entering a custom value, use the path relative to the forum root.
</div>,
<label className="checkbox">
<input type="radio" name="homePage" value="/all" checked={this.values.default_route() === '/all'} onclick={m.withAttr('value', this.values.default_route)}/>
All Discussions
</label>,
<label className="checkbox">
<input type="radio" name="homePage" value="custom" checked={this.values.default_route() !== '/all'} onclick={() => {
this.values.default_route('');
m.redraw(true);
this.$('.BasicsPage-homePage input').select();
}}/>
Custom <input className="FormControl" value={this.values.default_route()} onchange={m.withAttr('value', this.values.default_route)} style={this.values.default_route() !== '/all' ? 'margin-top: 5px' : 'display:none'}/>
</label>
]
})}
{FieldSet.component({
label: 'Welcome Banner',
className: 'BasicsPage-welcomeBanner',
children: [
<div className="helpText">
Configure the text that displays in the banner on the All Discussions page. Use this to welcome guests to your forum.
</div>,
<div className="BasicsPage-welcomeBanner-input">
<input className="FormControl" value={this.values.welcome_title()} oninput={m.withAttr('value', this.values.welcome_title)}/>
<textarea className="FormControl" value={this.values.welcome_message()} oninput={m.withAttr('value', this.values.welcome_message)}/>
</div>
]
})}
{Button.component({
type: 'submit',
className: 'Button Button--primary',
children: 'Save Changes',
loading: this.loading,
disabled: !this.changed()
})}
</form>
</div>
</div>
);
}
changed() {
const config = app.forum.attribute('config');
return this.fields.some(key => this.values[key]() !== config[key]);
}
onsubmit(e) {
e.preventDefault();
if (this.loading) return;
this.loading = true;
const config = {};
this.fields.forEach(key => config[key] = this.values[key]());
app.forum.save({config}).finally(() => {
this.loading = false;
m.redraw();
});
}
}

View File

@ -155,7 +155,7 @@ export default class Model {
return app.request({
method: this.exists ? 'PATCH' : 'POST',
url: app.forum.attribute('apiUrl') + '/' + this.data.type + (this.exists ? '/' + this.data.id : ''),
url: app.forum.attribute('apiUrl') + this.apiEndpoint(),
data: {data}
}).then(
// If everything went well, we'll make sure the store knows that this
@ -187,13 +187,23 @@ export default class Model {
return app.request({
method: 'DELETE',
url: app.forum.attribute('apiUrl') + '/' + this.data.type + '/' + this.data.id,
url: app.forum.attribute('apiUrl') + this.apiEndpoint(),
data
}).then(
() => this.exists = false
);
}
/**
* Construct a path to the API endpoint for this resource.
*
* @return {String}
* @protected
*/
apiEndpoint() {
return '/' + this.data.type + (this.exists ? '/' + this.data.id : '');
}
/**
* Generate a function which returns the value of the given attribute.
*

View File

@ -1,6 +1,7 @@
import Component from 'flarum/Component';
import icon from 'flarum/helpers/icon';
import extract from 'flarum/utils/extract';
import LoadingIndicator from 'flarum/components/LoadingIndicator';
/**
* The `Button` component defines an element which, when clicked, performs an
@ -11,6 +12,7 @@ import extract from 'flarum/utils/extract';
* - `disabled` Whether or not the button is disabled. If truthy, the button
* will be given a 'disabled' class name, and any `onclick` handler will be
* removed.
* - `loading` Whether or not the button should be in a disabled loading state.
*
* All other props will be assigned as attributes on the button element.
*
@ -28,8 +30,9 @@ export default class Button extends Component {
const iconName = extract(attrs, 'icon');
if (iconName) attrs.className += ' hasIcon';
if (attrs.disabled) {
attrs.className += ' disabled';
const loading = extract(attrs, 'loading');
if (attrs.disabled || loading) {
attrs.className += ' disabled' + (loading ? ' loading' : '');
delete attrs.onclick;
}
@ -47,7 +50,8 @@ export default class Button extends Component {
return [
iconName ? icon(iconName, {className: 'Button-icon'}) : '', ' ',
this.props.children ? <span className="Button-label">{this.props.children}</span> : ''
this.props.children ? <span className="Button-label">{this.props.children}</span> : '',
this.props.loading ? LoadingIndicator.component({size: 'tiny', className: 'LoadingIndicator--inline'}) : ''
];
}
}

View File

@ -1,3 +1,7 @@
import Model from 'flarum/Model';
export default class Forum extends Model {}
export default class Forum extends Model {
apiEndpoint() {
return '/forum';
}
}

View File

@ -0,0 +1,33 @@
.BasicsPage {
padding: 20px 0;
@media @desktop-up {
.container {
max-width: 600px;
margin: 0;
}
}
fieldset {
margin-bottom: 30px;
> ul {
list-style: none;
margin: 0;
padding: 0;
}
}
}
.BasicsPage-welcomeBanner-input {
:first-child {
margin-bottom: 1px;
border-bottom-right-radius: 0;
border-bottom-left-radius: 0;
font-weight: bold;
}
:last-child {
border-top-right-radius: 0;
border-top-left-radius: 0;
}
}

View File

@ -331,10 +331,5 @@
border-top: 0;
padding: 20px 0;
}
& .Button--primary {
padding-left: 20px;
padding-right: 20px;
}
}
}

View File

@ -86,6 +86,20 @@
pointer-events: none; // Future-proof disabling of clicks on `<a>` elements
}
}
.Button-label {
.transition(margin-right 0.1s);
}
.LoadingIndicator {
color: inherit;
margin: 0 -10px 0 -15px;
float: right;
}
&.loading {
.Button-label {
margin-right: 20px;
}
}
}
.Button--color(@color; @background) {
@ -155,6 +169,8 @@
.Button--primary {
.Button--color(@body-bg, @primary-color);
font-weight: bold;
padding-left: 20px;
padding-right: 20px;
.Button-icon {
display: none;

View File

@ -2,6 +2,7 @@
use Flarum\Api\Actions\SerializeResourceAction;
use Flarum\Api\JsonApiRequest;
use Flarum\Core\Groups\Group;
use Tobscure\JsonApi\Document;
class ShowAction extends SerializeResourceAction
@ -14,7 +15,9 @@ class ShowAction extends SerializeResourceAction
/**
* @inheritdoc
*/
public $include = [];
public $include = [
'groups' => true
];
/**
* @inheritdoc
@ -51,6 +54,10 @@ class ShowAction extends SerializeResourceAction
*/
protected function data(JsonApiRequest $request, Document $document)
{
return app('flarum.forum');
$forum = app('flarum.forum');
$forum->groups = Group::all();
return $forum;
}
}

View File

@ -0,0 +1,53 @@
<?php namespace Flarum\Api\Actions\Forum;
use Flarum\Core\Exceptions\PermissionDeniedException;
use Flarum\Core\Settings\SettingsRepository;
use Flarum\Api\Actions\SerializeResourceAction;
use Flarum\Api\JsonApiRequest;
use Tobscure\JsonApi\Document;
class UpdateAction extends SerializeResourceAction
{
/**
* @inheritdoc
*/
public $serializer = 'Flarum\Api\Serializers\ForumSerializer';
/**
* @var SettingsRepository
*/
protected $settings;
/**
* @param SettingsRepository $settings
*/
public function __construct(SettingsRepository $settings)
{
$this->settings = $settings;
}
/**
* Get the forum, ready to be serialized and assigned to the JsonApi
* response.
*
* @param JsonApiRequest $request
* @param Document $document
* @return \Flarum\Core\Forum
*/
protected function data(JsonApiRequest $request, Document $document)
{
if (! $request->actor->isAdmin()) {
throw new PermissionDeniedException;
}
$config = $request->get('data.attributes.config');
if (is_array($config)) {
foreach ($config as $k => $v) {
$this->settings->set($k, $v);
}
}
return app('flarum.forum');
}
}

View File

@ -94,6 +94,13 @@ class ApiServiceProvider extends ServiceProvider
$this->action('Flarum\Api\Actions\Forum\ShowAction')
);
// Save forum information
$routes->patch(
'/forum',
'flarum.api.forum.update',
$this->action('Flarum\Api\Actions\Forum\UpdateAction')
);
// Retrieve authentication token
$routes->post(
'/token',

View File

@ -1,6 +1,7 @@
<?php namespace Flarum\Api\Serializers;
use Flarum\Core;
use Flarum\Core\Groups\Permission;
class ForumSerializer extends Serializer
{
@ -22,13 +23,29 @@ class ForumSerializer extends Serializer
*/
protected function getDefaultAttributes($forum)
{
return [
'title' => $forum->title,
$attributes = [
'title' => Core::config('forum_title'),
'baseUrl' => Core::config('base_url'),
'apiUrl' => Core::config('api_url'),
'welcomeTitle' => Core::config('welcome_title'),
'welcomeMessage' => Core::config('welcome_message'),
'themePrimaryColor' => Core::config('theme_primary_color')
];
if ($this->actor->isAdmin()) {
$attributes['config'] = app('Flarum\Core\Settings\SettingsRepository')->all();
$attributes['availableLocales'] = app('flarum.localeManager')->getLocales();
$attributes['permissions'] = Permission::map();
}
return $attributes;
}
/**
* @return callable
*/
protected function groups()
{
return $this->hasMany('Flarum\Api\Serializers\GroupSerializer');
}
}

View File

@ -2,12 +2,24 @@
class LocaleManager
{
protected $locales = [];
protected $translations = [];
protected $js = [];
protected $config = [];
public function addLocale($locale, $name)
{
$this->locales[$locale] = $name;
}
public function getLocales()
{
return $this->locales;
}
public function addTranslations($locale, $translations)
{
if (! isset($this->translations[$locale])) {

View File

@ -14,15 +14,16 @@ class LocaleServiceProvider extends ServiceProvider
{
$manager = $this->app->make('flarum.localeManager');
$this->registerLocale($manager, 'en');
$this->registerLocale($manager, 'en', 'English');
event(new RegisterLocales($manager));
}
public function registerLocale(LocaleManager $manager, $locale)
public function registerLocale(LocaleManager $manager, $locale, $title)
{
$path = __DIR__.'/../../locale/'.$locale;
$manager->addLocale($locale, $title);
$manager->addTranslations($locale, $path.'.yml');
$manager->addConfig($locale, $path.'.php');
$manager->addJsFile($locale, $path.'.js');