mirror of
https://github.com/flarum/framework.git
synced 2025-05-22 22:59:57 +08:00
Implement basic settings page
This commit is contained in:
137
js/admin/src/components/BasicsPage.js
Normal file
137
js/admin/src/components/BasicsPage.js
Normal 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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -155,7 +155,7 @@ export default class Model {
|
|||||||
|
|
||||||
return app.request({
|
return app.request({
|
||||||
method: this.exists ? 'PATCH' : 'POST',
|
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}
|
data: {data}
|
||||||
}).then(
|
}).then(
|
||||||
// If everything went well, we'll make sure the store knows that this
|
// If everything went well, we'll make sure the store knows that this
|
||||||
@ -187,13 +187,23 @@ export default class Model {
|
|||||||
|
|
||||||
return app.request({
|
return app.request({
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
url: app.forum.attribute('apiUrl') + '/' + this.data.type + '/' + this.data.id,
|
url: app.forum.attribute('apiUrl') + this.apiEndpoint(),
|
||||||
data
|
data
|
||||||
}).then(
|
}).then(
|
||||||
() => this.exists = false
|
() => 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.
|
* Generate a function which returns the value of the given attribute.
|
||||||
*
|
*
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import Component from 'flarum/Component';
|
import Component from 'flarum/Component';
|
||||||
import icon from 'flarum/helpers/icon';
|
import icon from 'flarum/helpers/icon';
|
||||||
import extract from 'flarum/utils/extract';
|
import extract from 'flarum/utils/extract';
|
||||||
|
import LoadingIndicator from 'flarum/components/LoadingIndicator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The `Button` component defines an element which, when clicked, performs an
|
* 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
|
* - `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
|
* will be given a 'disabled' class name, and any `onclick` handler will be
|
||||||
* removed.
|
* 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.
|
* 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');
|
const iconName = extract(attrs, 'icon');
|
||||||
if (iconName) attrs.className += ' hasIcon';
|
if (iconName) attrs.className += ' hasIcon';
|
||||||
|
|
||||||
if (attrs.disabled) {
|
const loading = extract(attrs, 'loading');
|
||||||
attrs.className += ' disabled';
|
if (attrs.disabled || loading) {
|
||||||
|
attrs.className += ' disabled' + (loading ? ' loading' : '');
|
||||||
delete attrs.onclick;
|
delete attrs.onclick;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,7 +50,8 @@ export default class Button extends Component {
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
iconName ? icon(iconName, {className: 'Button-icon'}) : '', ' ',
|
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'}) : ''
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,7 @@
|
|||||||
import Model from 'flarum/Model';
|
import Model from 'flarum/Model';
|
||||||
|
|
||||||
export default class Forum extends Model {}
|
export default class Forum extends Model {
|
||||||
|
apiEndpoint() {
|
||||||
|
return '/forum';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
33
less/admin/BasicsPage.less
Normal file
33
less/admin/BasicsPage.less
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -331,10 +331,5 @@
|
|||||||
border-top: 0;
|
border-top: 0;
|
||||||
padding: 20px 0;
|
padding: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
& .Button--primary {
|
|
||||||
padding-left: 20px;
|
|
||||||
padding-right: 20px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,6 +86,20 @@
|
|||||||
pointer-events: none; // Future-proof disabling of clicks on `<a>` elements
|
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) {
|
.Button--color(@color; @background) {
|
||||||
@ -155,6 +169,8 @@
|
|||||||
.Button--primary {
|
.Button--primary {
|
||||||
.Button--color(@body-bg, @primary-color);
|
.Button--color(@body-bg, @primary-color);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
|
padding-left: 20px;
|
||||||
|
padding-right: 20px;
|
||||||
|
|
||||||
.Button-icon {
|
.Button-icon {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use Flarum\Api\Actions\SerializeResourceAction;
|
use Flarum\Api\Actions\SerializeResourceAction;
|
||||||
use Flarum\Api\JsonApiRequest;
|
use Flarum\Api\JsonApiRequest;
|
||||||
|
use Flarum\Core\Groups\Group;
|
||||||
use Tobscure\JsonApi\Document;
|
use Tobscure\JsonApi\Document;
|
||||||
|
|
||||||
class ShowAction extends SerializeResourceAction
|
class ShowAction extends SerializeResourceAction
|
||||||
@ -14,7 +15,9 @@ class ShowAction extends SerializeResourceAction
|
|||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
public $include = [];
|
public $include = [
|
||||||
|
'groups' => true
|
||||||
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
@ -51,6 +54,10 @@ class ShowAction extends SerializeResourceAction
|
|||||||
*/
|
*/
|
||||||
protected function data(JsonApiRequest $request, Document $document)
|
protected function data(JsonApiRequest $request, Document $document)
|
||||||
{
|
{
|
||||||
return app('flarum.forum');
|
$forum = app('flarum.forum');
|
||||||
|
|
||||||
|
$forum->groups = Group::all();
|
||||||
|
|
||||||
|
return $forum;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
53
src/Api/Actions/Forum/UpdateAction.php
Normal file
53
src/Api/Actions/Forum/UpdateAction.php
Normal 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');
|
||||||
|
}
|
||||||
|
}
|
@ -94,6 +94,13 @@ class ApiServiceProvider extends ServiceProvider
|
|||||||
$this->action('Flarum\Api\Actions\Forum\ShowAction')
|
$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
|
// Retrieve authentication token
|
||||||
$routes->post(
|
$routes->post(
|
||||||
'/token',
|
'/token',
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
<?php namespace Flarum\Api\Serializers;
|
<?php namespace Flarum\Api\Serializers;
|
||||||
|
|
||||||
use Flarum\Core;
|
use Flarum\Core;
|
||||||
|
use Flarum\Core\Groups\Permission;
|
||||||
|
|
||||||
class ForumSerializer extends Serializer
|
class ForumSerializer extends Serializer
|
||||||
{
|
{
|
||||||
@ -22,13 +23,29 @@ class ForumSerializer extends Serializer
|
|||||||
*/
|
*/
|
||||||
protected function getDefaultAttributes($forum)
|
protected function getDefaultAttributes($forum)
|
||||||
{
|
{
|
||||||
return [
|
$attributes = [
|
||||||
'title' => $forum->title,
|
'title' => Core::config('forum_title'),
|
||||||
'baseUrl' => Core::config('base_url'),
|
'baseUrl' => Core::config('base_url'),
|
||||||
'apiUrl' => Core::config('api_url'),
|
'apiUrl' => Core::config('api_url'),
|
||||||
'welcomeTitle' => Core::config('welcome_title'),
|
'welcomeTitle' => Core::config('welcome_title'),
|
||||||
'welcomeMessage' => Core::config('welcome_message'),
|
'welcomeMessage' => Core::config('welcome_message'),
|
||||||
'themePrimaryColor' => Core::config('theme_primary_color')
|
'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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,24 @@
|
|||||||
|
|
||||||
class LocaleManager
|
class LocaleManager
|
||||||
{
|
{
|
||||||
|
protected $locales = [];
|
||||||
|
|
||||||
protected $translations = [];
|
protected $translations = [];
|
||||||
|
|
||||||
protected $js = [];
|
protected $js = [];
|
||||||
|
|
||||||
protected $config = [];
|
protected $config = [];
|
||||||
|
|
||||||
|
public function addLocale($locale, $name)
|
||||||
|
{
|
||||||
|
$this->locales[$locale] = $name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLocales()
|
||||||
|
{
|
||||||
|
return $this->locales;
|
||||||
|
}
|
||||||
|
|
||||||
public function addTranslations($locale, $translations)
|
public function addTranslations($locale, $translations)
|
||||||
{
|
{
|
||||||
if (! isset($this->translations[$locale])) {
|
if (! isset($this->translations[$locale])) {
|
||||||
|
@ -14,15 +14,16 @@ class LocaleServiceProvider extends ServiceProvider
|
|||||||
{
|
{
|
||||||
$manager = $this->app->make('flarum.localeManager');
|
$manager = $this->app->make('flarum.localeManager');
|
||||||
|
|
||||||
$this->registerLocale($manager, 'en');
|
$this->registerLocale($manager, 'en', 'English');
|
||||||
|
|
||||||
event(new RegisterLocales($manager));
|
event(new RegisterLocales($manager));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function registerLocale(LocaleManager $manager, $locale)
|
public function registerLocale(LocaleManager $manager, $locale, $title)
|
||||||
{
|
{
|
||||||
$path = __DIR__.'/../../locale/'.$locale;
|
$path = __DIR__.'/../../locale/'.$locale;
|
||||||
|
|
||||||
|
$manager->addLocale($locale, $title);
|
||||||
$manager->addTranslations($locale, $path.'.yml');
|
$manager->addTranslations($locale, $path.'.yml');
|
||||||
$manager->addConfig($locale, $path.'.php');
|
$manager->addConfig($locale, $path.'.php');
|
||||||
$manager->addJsFile($locale, $path.'.js');
|
$manager->addJsFile($locale, $path.'.js');
|
||||||
|
Reference in New Issue
Block a user