Blade -> Twig (wip)

This commit is contained in:
Pig Fang 2019-09-17 23:10:44 +08:00
parent 19efd013f6
commit 9403ae356d
83 changed files with 2012 additions and 1002 deletions

View File

@ -10,6 +10,8 @@ jobs:
- uses: actions/checkout@v1
- name: Install dependencies
run: composer install --prefer-dist --no-progress --no-suggest
- name: Validate Twig templates
run: php artisan twig:lint -v
frontend:
name: JavaScript Check
runs-on: ubuntu-latest

View File

@ -21,6 +21,18 @@ use Illuminate\Support\Facades\Redis;
class AdminController extends Controller
{
public function index()
{
return view('admin.index', [
'sum' => [
'users' => User::count(),
'players' => Player::count(),
'textures' => Texture::count(),
'storage' => Texture::select('size')->sum('size'),
],
]);
}
public function chartData()
{
$today = Carbon::today()->timestamp;
@ -105,7 +117,7 @@ class AdminController extends Controller
return redirect('/admin');
}
public function customize(Request $request)
public function customize(Request $request, \App\Services\Webpack $webpack)
{
$homepage = Option::form('homepage', OptionForm::AUTO_DETECT, function ($form) {
$form->text('home_pic_url')->hint();
@ -142,7 +154,14 @@ class AdminController extends Controller
option(['color_scheme' => $color]);
}
return view('admin.customize', ['forms' => compact('homepage', 'customJsCss')]);
return view('admin.customize', [
'colors' => ['blue', 'yellow', 'green', 'purple', 'red', 'black'],
'skins_css' => $webpack->url('skins/_all-skins.min.css'),
'forms' => [
'homepage' => $homepage,
'custom_js_css' => $customJsCss,
],
]);
}
public function score()

View File

@ -41,21 +41,26 @@ class UserController extends Controller
$user = Auth::user();
[$from, $to] = explode(',', option('sign_score'));
$scoreIntro = nl2br(trans('user.score-intro.introduction', [
$scoreIntro = trans('user.score-intro.introduction', [
'initial_score' => option('user_initial_score'),
'score-from' => $from,
'score-to' => $to,
'return-score' => option('return_score')
? trans('user.score-intro.will-return-score')
: trans('user.score-intro.no-return-score'),
]));
]);
return view('user.index')->with([
'statistics' => [
'players' => $this->calculatePercentageUsed($user->players->count(), option('score_per_player')),
'storage' => $this->calculatePercentageUsed($this->getStorageUsed($user), option('score_per_storage')),
],
'scoreIntro' => $scoreIntro,
'score_intro' => $scoreIntro,
'rates' => [
'storage' => option('score_per_storage'),
'player' => option('score_per_player'),
'closet' => option('score_per_closet_item'),
],
'announcement' => app('parsedown')->text(option_localized('announcement')),
'extra' => ['unverified' => option('require_verification') && ! $user->verified],
]);

View File

@ -0,0 +1,70 @@
<?php
namespace App\Http\View\Composers;
use App\Models\User;
use App\Services\Webpack;
use Illuminate\View\View;
use Illuminate\Http\Request;
use App\Services\Translations\JavaScript;
use Illuminate\Contracts\Events\Dispatcher;
class FootComposer
{
/** @var Request */
protected $request;
/** @var Webpack */
protected $webpack;
/** @var JavaScript */
protected $javascript;
/** @var Dispatcher */
protected $dispatcher;
public function __construct(
Request $request,
Webpack $webpack,
JavaScript $javascript,
Dispatcher $dispatcher
) {
$this->request = $request;
$this->webpack = $webpack;
$this->javascript = $javascript;
$this->dispatcher = $dispatcher;
}
public function compose(View $view)
{
$this->injectJavaScript($view);
$this->addExtra($view);
}
public function injectJavaScript(View $view)
{
$scripts = [];
$locale = app()->getLocale();
$scripts[] = $this->javascript->generate($locale);
if ($pluginI18n = $this->javascript->plugin($locale)) {
$scripts[] = $pluginI18n;
}
if ($this->request->is('admin*') && auth()->user()->permission >= User::SUPER_ADMIN) {
$scripts[] = $this->webpack->url('check-updates.js');
}
$scripts[] = $this->webpack->url('index.js');
$view->with([
'scripts' => $scripts,
'inline_js' => option('custom_js'),
]);
}
public function addExtra(View $view)
{
$content = [];
$this->dispatcher->dispatch(new \App\Events\RenderingFooter($content));
$view->with('extra_foot', $content);
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace App\Http\View\Composers;
use Illuminate\View\View;
use App\Services\Webpack;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
use Illuminate\Contracts\Events\Dispatcher;
class HeadComposer
{
/** @var Webpack */
protected $webpack;
/** @var Dispatcher */
protected $dispatcher;
public function __construct(Webpack $webpack, Dispatcher $dispatcher)
{
$this->webpack = $webpack;
$this->dispatcher = $dispatcher;
}
public function compose(View $view)
{
$this->addFavicon($view);
$this->applyThemeColor($view);
$this->seo($view);
$this->injectStyles($view);
$this->addExtra($view);
}
public function addFavicon(View $view)
{
$url = option('favicon_url', config('options.favicon_url'));
$url = Str::startsWith($url, 'http') ? $url : url($url);
$view->with('favicon', $url);
}
public function applyThemeColor(View $view)
{
$colors = [
'blue' => '#3c8dbc',
'yellow' => '#f39c12',
'green' => '#00a65a',
'purple' => '#605ca8',
'red' => '#dd4b39',
'black' => '#ffffff',
];
preg_match('/skin-(\w+)?(?:-light)?/', option('color_scheme'), $matches);
$view->with('theme_color', Arr::get($colors, $matches[1]));
}
public function seo(View $view)
{
$view->with('seo', [
'keywords' => option('meta_keywords'),
'description' => option('meta_description'),
'extra' => option('meta_extras'),
]);
}
public function injectStyles(View $view)
{
$view->with('styles', [
$this->webpack->url('style.css'),
$this->webpack->url('skins/'.option('color_scheme').'.min.css'),
]);
$view->with('inline_css', option('custom_css'));
}
public function addExtra(View $view)
{
$content = [];
$this->dispatcher->dispatch(new \App\Events\RenderingHeader($content));
$view->with('extra_head', $content);
}
}

View File

@ -0,0 +1,40 @@
<?php
namespace App\Http\View\Composers;
use Illuminate\View\View;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
class LanguagesMenuComposer
{
/** @var Request */
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
}
public function compose(View $view)
{
$query = $this->request->query();
$url = $this->request->url();
$langs = collect(config('locales'))
->reject(function ($locale) {
return Arr::has($locale, 'alias');
})
->map(function ($locale, $id) use ($query, $url) {
$query = array_merge($query, ['lang' => $id]);
$locale['url'] = $url.'?'.http_build_query($query);
return $locale;
});
$view->with([
'current' => config('locales.'.app()->getLocale().'.short_name'),
'langs' => $langs,
]);
}
}

View File

@ -0,0 +1,101 @@
<?php
namespace App\Http\View\Composers;
use App\Events;
use Illuminate\View\View;
use Illuminate\Support\Arr;
use Illuminate\Http\Request;
use App\Services\PluginManager;
class SideMenuComposer
{
/** @var Request */
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
}
public function compose(View $view)
{
$type = $view->gatherData()['type'];
$menu = config('menu');
switch ($type) {
case 'user':
event(new Events\ConfigureUserMenu($menu));
break;
case 'explore':
event(new Events\ConfigureExploreMenu($menu));
break;
case 'admin':
event(new Events\ConfigureAdminMenu($menu));
$menu['admin'] = $this->collectPluginConfigs($menu['admin']);
break;
}
$view->with('items', array_map(function ($item) {
return $this->transform($item);
}, $menu[$type]));
}
public function transform(array $item): array
{
$isActive = $this->request->is(Arr::get($item, 'link'));
foreach (Arr::get($item, 'children', []) as $k => $v) {
if ($this->request->is(Arr::get($v, 'link'))) {
$isActive = true;
break;
}
}
$classes = [];
if ($isActive) {
$classes[] = 'active menu-open';
}
if (Arr::has($item, 'children')) {
$classes[] = 'treeview';
$item['children'] = array_map(function ($item) {
return $this->transform($item);
}, $item['children']);
}
$item['classes'] = $classes;
return $item;
}
public function collectPluginConfigs(array &$menu)
{
$menu = array_map(function ($item) {
if (Arr::get($item, 'id') === 'plugin-configs') {
$pluginConfigs = resolve(PluginManager::class)
->getEnabledPlugins()
->filter(function ($plugin) {
return $plugin->hasConfigView();
})
->map(function ($plugin) {
return [
'title' => trans($plugin->title),
'link' => 'admin/plugins/config/'.$plugin->name,
'icon' => 'fa-circle',
];
});
// Don't display this menu item when no plugin config is available
if ($pluginConfigs->isNotEmpty()) {
$item['children'] = array_merge($item['children'], $pluginConfigs->values()->all());
return $item;
}
} else {
return $item;
}
}, $menu);
return array_filter($menu); // Remove empty items
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace App\Http\View\Composers;
use Illuminate\View\View;
use Illuminate\Http\Request;
class UserMenuComposer
{
/** @var Request */
protected $request;
public function __construct(Request $request)
{
$this->request = $request;
}
public function compose(View $view)
{
$user = auth()->user();
$view->with('user', $user);
if ($this->request->is('skinlib*') || $this->request->is('/')) {
$view->with(
'tiny_avatar',
url('avatar/25/'.base64_encode($user->email).'.png?tid='.$user->avatar)
);
}
$view->with(
'avatar',
url('avatar/128/'.base64_encode($user->email).'.png?tid='.$user->avatar)
);
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace App\Http\View\Composers;
use App\Models\User;
use Illuminate\View\View;
use Illuminate\Contracts\Events\Dispatcher;
class UserPanelComposer
{
/** @var Dispatcher */
protected $dispatcher;
public function __construct(Dispatcher $dispatcher)
{
$this->dispatcher = $dispatcher;
}
public function compose(View $view)
{
$user = auth()->user();
$roles = [
User::BANNED => 'banned',
User::NORMAL => 'normal',
User::ADMIN => 'admin',
User::SUPER_ADMIN => 'super-admin',
];
$role = $roles[$user->permission];
$avatar = url('avatar/45/'.base64_encode($user->email).'.png?tid='.$user->avatar);
$badges = [];
$this->dispatcher->dispatch(new \App\Events\RenderingBadges($badges));
$view->with([
'user' => $user,
'role' => trans("admin.users.status.$role"),
'avatar' => $avatar,
'badges' => $badges,
]);
}
}

View File

@ -0,0 +1,54 @@
<?php
namespace App\Providers;
use View;
use App\Http\View\Composers;
use Illuminate\Support\ServiceProvider;
class ViewServiceProvider extends ServiceProvider
{
public function boot()
{
View::composer(['*.base', 'shared.header'], function ($view) {
$view->with([
'site_name' => option_localized('site_name'),
'color_scheme' => option('color_scheme'),
]);
});
View::composer('shared.head', Composers\HeadComposer::class);
View::composer('shared.notifications', function ($view) {
$notifications = auth()->user()->unreadNotifications;
$view->with([
'notifications' => $notifications,
'amount' => count($notifications),
]);
});
View::composer('shared.languages', Composers\LanguagesMenuComposer::class);
View::composer('shared.user-menu', Composers\UserMenuComposer::class);
View::composer('shared.side-menu', Composers\SideMenuComposer::class);
View::composer('shared.user-panel', Composers\UserPanelComposer::class);
View::composer('shared.footer', function ($view) {
$customCopyright = get_string_replaced(
option_localized('copyright_text'),
[
'{site_name}' => option_localized('site_name'),
'{site_url}' => option('site_url'),
]
);
$view->with([
'copyright' => option_localized('copyright_prefer', 0),
'custom_copyright' => $customCopyright,
]);
});
View::composer('shared.foot', Composers\FootComposer::class);
}
}

View File

@ -77,102 +77,6 @@ if (! function_exists('bs_header_extra')) {
}
}
if (! function_exists('bs_menu')) {
function bs_menu(string $type): string
{
$menu = config('menu');
switch ($type) {
case 'user':
event(new App\Events\ConfigureUserMenu($menu));
break;
case 'explore':
event(new App\Events\ConfigureExploreMenu($menu));
break;
case 'admin':
event(new App\Events\ConfigureAdminMenu($menu));
break;
}
if (! isset($menu[$type])) {
throw new InvalidArgumentException;
}
$menu[$type] = array_map(function ($item) {
if (Arr::get($item, 'id') === 'plugin-configs') {
$pluginConfigs = app('plugins')->getEnabledPlugins()
->filter(function ($plugin) {
return $plugin->hasConfigView();
})
->map(function ($plugin) {
return [
'title' => trans($plugin->title),
'link' => 'admin/plugins/config/'.$plugin->name,
'icon' => 'fa-circle',
];
});
// Don't display this menu item when no plugin config is available
if ($pluginConfigs->isNotEmpty()) {
$item['children'] = array_merge($item['children'], $pluginConfigs->values()->all());
return $item;
}
} else {
return $item;
}
}, $menu[$type]);
return bs_menu_render($menu[$type]);
}
function bs_menu_render(array $data): string
{
$content = '';
foreach ($data as $key => $value) {
$active = app('request')->is(@$value['link']);
// also set parent as active if any child is active
foreach ((array) @$value['children'] as $childKey => $childValue) {
if (app('request')->is(@$childValue['link'])) {
$active = true;
}
}
$classes = [];
$active ? ($classes[] = 'active menu-open') : null;
isset($value['children']) ? ($classes[] = 'treeview') : null;
$attr = count($classes) ? sprintf(' class="%s"', implode(' ', $classes)) : '';
$content .= "<li{$attr}>";
if (isset($value['children'])) {
$content .= sprintf('<a href="#"><i class="fas %s"></i> &nbsp;<span>%s</span><span class="pull-right-container"><i class="fas fa-angle-left pull-right"></i></span></a>', $value['icon'], trans($value['title']));
// recurse
$content .= '<ul class="treeview-menu">'.bs_menu_render($value['children']).'</ul>';
} else {
if ($value) {
$content .= sprintf(
'<a href="%s" %s><i class="%s %s"></i> &nbsp;<span>%s</span></a>',
url((string) $value['link']),
Arr::get($value, 'new-tab') ? 'target="_blank"' : '',
$value['icon'] == 'fa-circle' ? 'far' : 'fas',
(string) $value['icon'],
trans((string) $value['title'])
);
}
}
$content .= '</li>';
}
return $content;
}
}
if (! function_exists('bs_copyright')) {
function bs_copyright(): string
{
@ -323,22 +227,6 @@ if (! function_exists('is_request_secure')) {
}
}
if (! function_exists('nl2p')) {
/**
* Wrap blocks of text (delimited by \n) in p tags (similar to nl2br).
*
* @param string $text
* @return string
*/
function nl2p(string $text): string
{
$parts = explode("\n", $text);
$result = '<p>'.implode('</p><p>', $parts).'</p>';
// Remove empty paragraphs
return str_replace('<p></p>', '', $result);
}
}
if (! function_exists('png')) {
function png($resource)
{

View File

@ -26,7 +26,9 @@
"composer/ca-bundle": "^1.2",
"facade/ignition": "^1.4",
"spatie/laravel-translation-loader": "^2.4",
"symfony/yaml": "^4.3"
"symfony/yaml": "^4.3",
"twig/twig": "^2.11",
"rcrowe/twigbridge": "^0.11.1"
},
"require-dev": {
"fzaninotto/faker": "~1.8",

141
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "e0bda645aa2fc04fc9dc1d6e0d711c50",
"content-hash": "5ac658b15e8a3bf5b2f92d73193abf8a",
"packages": [
{
"name": "composer/ca-bundle",
@ -2577,6 +2577,78 @@
],
"time": "2018-07-19T23:38:55+00:00"
},
{
"name": "rcrowe/twigbridge",
"version": "v0.11.1",
"source": {
"type": "git",
"url": "https://github.com/rcrowe/TwigBridge.git",
"reference": "2054abdbd8dcde573cd86d8d3856873e2388d47b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/rcrowe/TwigBridge/zipball/2054abdbd8dcde573cd86d8d3856873e2388d47b",
"reference": "2054abdbd8dcde573cd86d8d3856873e2388d47b",
"shasum": ""
},
"require": {
"illuminate/support": "5.5.*|5.6.*|5.7.*|5.8.x|6.0.*",
"illuminate/view": "5.5.*|5.6.*|5.7.*|5.8.x|6.0.*",
"php": ">=7.1",
"twig/twig": "~2.0"
},
"require-dev": {
"laravel/framework": "5.5.*",
"mockery/mockery": "0.9.*",
"phpunit/phpunit": "~6.0",
"satooshi/php-coveralls": "~0.6",
"squizlabs/php_codesniffer": "~1.5"
},
"suggest": {
"laravelcollective/html": "For bringing back html/form in Laravel 5.x",
"twig/extensions": "~1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "0.11-dev"
},
"laravel": {
"providers": [
"TwigBridge\\ServiceProvider"
],
"aliases": {
"Twig": "TwigBridge\\Facade\\Twig"
}
}
},
"autoload": {
"psr-4": {
"TwigBridge\\": "src",
"TwigBridge\\Tests\\": "tests"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Rob Crowe",
"email": "hello@vivalacrowe.com"
},
{
"name": "Barry vd. Heuvel",
"email": "barryvdh@gmail.com"
}
],
"description": "Adds the power of Twig to Laravel",
"keywords": [
"laravel",
"twig"
],
"time": "2019-09-05T11:59:53+00:00"
},
{
"name": "scrivo/highlight.php",
"version": "v9.15.10.0",
@ -4356,6 +4428,73 @@
"homepage": "https://github.com/tijsverkoyen/CssToInlineStyles",
"time": "2017-11-27T11:13:29+00:00"
},
{
"name": "twig/twig",
"version": "v2.11.3",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "699ed2342557c88789a15402de5eb834dedd6792"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/699ed2342557c88789a15402de5eb834dedd6792",
"reference": "699ed2342557c88789a15402de5eb834dedd6792",
"shasum": ""
},
"require": {
"php": "^7.0",
"symfony/polyfill-ctype": "^1.8",
"symfony/polyfill-mbstring": "^1.3"
},
"require-dev": {
"psr/container": "^1.0",
"symfony/debug": "^2.7",
"symfony/phpunit-bridge": "^3.4.19|^4.1.8|^5.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "2.11-dev"
}
},
"autoload": {
"psr-0": {
"Twig_": "lib/"
},
"psr-4": {
"Twig\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com",
"homepage": "http://fabien.potencier.org",
"role": "Lead Developer"
},
{
"name": "Armin Ronacher",
"email": "armin.ronacher@active-4.com",
"role": "Project Founder"
},
{
"name": "Twig Team",
"homepage": "https://twig.symfony.com/contributors",
"role": "Contributors"
}
],
"description": "Twig, the flexible, fast, and secure template language for PHP",
"homepage": "https://twig.symfony.com",
"keywords": [
"templating"
],
"time": "2019-06-18T15:37:11+00:00"
},
{
"name": "tymon/jwt-auth",
"version": "dev-develop",

View File

@ -188,7 +188,7 @@ return [
App\Providers\PluginServiceProvider::class,
App\Providers\RouteServiceProvider::class,
App\Providers\ValidatorExtendServiceProvider::class,
App\Providers\ViewServiceProvider::class,
],
/*

214
config/twigbridge.php Normal file
View File

@ -0,0 +1,214 @@
<?php
/**
* This file is part of the TwigBridge package.
*
* @copyright Robert Crowe <hello@vivalacrowe.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Configuration options for Twig.
*/
return [
'twig' => [
/*
|--------------------------------------------------------------------------
| Extension
|--------------------------------------------------------------------------
|
| File extension for Twig view files.
|
*/
'extension' => 'twig',
/*
|--------------------------------------------------------------------------
| Accepts all Twig environment configuration options
|--------------------------------------------------------------------------
|
| http://twig.sensiolabs.org/doc/api.html#environment-options
|
*/
'environment' => [
// When set to true, the generated templates have a __toString() method
// that you can use to display the generated nodes.
// default: false
'debug' => env('APP_DEBUG', false),
// The charset used by the templates.
// default: utf-8
'charset' => 'utf-8',
// The base template class to use for generated templates.
// default: TwigBridge\Twig\Template
'base_template_class' => 'TwigBridge\Twig\Template',
// An absolute path where to store the compiled templates, or false to disable caching. If null
// then the cache file path is used.
// default: cache file storage path
'cache' => null,
// When developing with Twig, it's useful to recompile the template
// whenever the source code changes. If you don't provide a value
// for the auto_reload option, it will be determined automatically based on the debug value.
'auto_reload' => true,
// If set to false, Twig will silently ignore invalid variables
// (variables and or attributes/methods that do not exist) and
// replace them with a null value. When set to true, Twig throws an exception instead.
// default: false
'strict_variables' => false,
// If set to true, auto-escaping will be enabled by default for all templates.
// default: 'html'
'autoescape' => 'html',
// A flag that indicates which optimizations to apply
// (default to -1 -- all optimizations are enabled; set it to 0 to disable)
'optimizations' => -1,
],
/*
|--------------------------------------------------------------------------
| Global variables
|--------------------------------------------------------------------------
|
| These will always be passed in and can be accessed as Twig variables.
| NOTE: these will be overwritten if you pass data into the view with the same key.
|
*/
'globals' => [],
],
'extensions' => [
/*
|--------------------------------------------------------------------------
| Extensions
|--------------------------------------------------------------------------
|
| Enabled extensions.
|
| `Twig\Extension\DebugExtension` is enabled automatically if twig.debug is TRUE.
|
*/
'enabled' => [
'Twig\Extension\StringLoaderExtension',
'TwigBridge\Extension\Loader\Facades',
'TwigBridge\Extension\Loader\Filters',
'TwigBridge\Extension\Loader\Functions',
'TwigBridge\Extension\Laravel\Auth',
'TwigBridge\Extension\Laravel\Config',
'TwigBridge\Extension\Laravel\Dump',
// 'TwigBridge\Extension\Laravel\Input',
'TwigBridge\Extension\Laravel\Session',
// 'TwigBridge\Extension\Laravel\Str',
'TwigBridge\Extension\Laravel\Translator',
'TwigBridge\Extension\Laravel\Url',
// 'TwigBridge\Extension\Laravel\Model',
// 'TwigBridge\Extension\Laravel\Gate',
// 'TwigBridge\Extension\Laravel\Form',
// 'TwigBridge\Extension\Laravel\Html',
// 'TwigBridge\Extension\Laravel\Legacy\Facades',
],
/*
|--------------------------------------------------------------------------
| Facades
|--------------------------------------------------------------------------
|
| Available facades. Access like `{{ Config.get('foo.bar') }}`.
|
| Each facade can take an optional array of options. To mark the whole facade
| as safe you can set the option `'is_safe' => true`. Setting the facade as
| safe means that any HTML returned will not be escaped.
|
| It is advisable to not set the whole facade as safe and instead mark the
| each appropriate method as safe for security reasons. You can do that with
| the following syntax:
|
| <code>
| 'Form' => [
| 'is_safe' => [
| 'open'
| ]
| ]
| </code>
|
| The values of the `is_safe` array must match the called method on the facade
| in order to be marked as safe.
|
*/
'facades' => [],
/*
|--------------------------------------------------------------------------
| Functions
|--------------------------------------------------------------------------
|
| Available functions. Access like `{{ secure_url(...) }}`.
|
| Each function can take an optional array of options. These options are
| passed directly to `Twig\TwigFunction`.
|
| So for example, to mark a function as safe you can do the following:
|
| <code>
| 'link_to' => [
| 'is_safe' => ['html']
| ]
| </code>
|
| The options array also takes a `callback` that allows you to name the
| function differently in your Twig templates than what it's actually called.
|
| <code>
| 'link' => [
| 'callback' => 'link_to'
| ]
| </code>
|
*/
'functions' => [],
/*
|--------------------------------------------------------------------------
| Filters
|--------------------------------------------------------------------------
|
| Available filters. Access like `{{ variable|filter }}`.
|
| Each filter can take an optional array of options. These options are
| passed directly to `Twig\TwigFilter`.
|
| So for example, to mark a filter as safe you can do the following:
|
| <code>
| 'studly_case' => [
| 'is_safe' => ['html']
| ]
| </code>
|
| The options array also takes a `callback` that allows you to name the
| filter differently in your Twig templates than what is actually called.
|
| <code>
| 'snake' => [
| 'callback' => 'snake_case'
| ]
| </code>
|
*/
'filters' => [
'get' => 'data_get',
],
],
];

View File

@ -57,6 +57,9 @@ body, h1, h2, h3, h4, h5, h6
.main-header .navbar .user-menu .user-image
border-radius 10%
.navbar-nav > .user-menu > .dropdown-menu > li.user-header
height unset
#language-menu li
a
color #777

View File

@ -159,15 +159,16 @@ update:
latest: "Latest Version:"
current: "Current Version:"
check-github: <a href=":url" target="_blank" class="el-button pull-right">Check GitHub Releases</a>
check-github: Check GitHub Releases
button: Update Now
cautions:
title: Cautions
link: check out this.
text: |
Please choose update source according to your host's network environment.
Low-speed connection between update source and your host will cause long-time loading at checking and downloading page.
To change the default update source, please refer to <a target="_blank" href="https://blessing.netlify.com/update-sources.html">this article</a>.
To change the default update source,
download:
downloading: Downloading update package...

View File

@ -164,15 +164,16 @@ update:
latest: 最新版本:
current: 当前版本:
check-github: <a href=":url" target="_blank" class="el-button pull-right">查看 GitHub Releases</a>
check-github: 查看 GitHub Releases
button: 马上升级
cautions:
title: 注意事项
link: 点击了解详情
text: |
请根据你的主机所在位置(国内/国外)选择更新源。
如错选至相对于你的主机速度较慢的源,可能会造成检查与下载更新页面长时间无响应。
如何更换更新源?<a target="_blank" href="https://blessing.netlify.com/update-sources.html">点击了解详情</a>。
如何更换更新源?
download:
downloading: 正在下载更新包

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
{{ include('shared.head') }}
<title>{% block title %}{% endblock %} - {{ site_name }}</title>
</head>
<body class="hold-transition {{ color_scheme }} sidebar-mini">
<div class="wrapper">
{{ include('shared.header') }}
{{ include('shared.sidebar', {scope: 'admin'}) }}
<div class="content-wrapper">
<section class="content-header">
<h1>{{ block('title') }}</h1>
</section>
<section class="content">
{% block content %}{% endblock %}
</section>
</div>
{{ include('shared.footer') }}
</div>
{% block before_foot %}{% endblock %}
{{ include('shared.foot') }}
</body>
</html>

View File

@ -1,81 +0,0 @@
@extends('admin.master')
@section('title', trans('general.customize'))
@section('style')
<link rel="stylesheet" href="{{ webpack_assets('skins/_all-skins.min.css') }}">
@endsection
@section('content')
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
@lang('general.customize')
</h1>
</section>
<!-- Main content -->
<section class="content">
<div class="row">
<div class="col-md-3">
<form class="box box-primary" method="post" action="{{ url('/admin/customize?action=color') }}">
@csrf
<div class="box-header with-border">
<h3 v-t="'admin.change-color.title'" class="box-title">
@lang('admin.customize.change-color.title')
</h3>
</div>
<div class="box-body no-padding">
<table class="table table-striped bring-up nth-2-center" id="change-color">
@php
$colors = ['blue', 'yellow', 'green', 'purple', 'red', 'black'];
@endphp
<tbody>
@foreach ($colors as $color)
<tr>
<td>@lang('admin.customize.colors.'.$color)</td>
<td>
<label>
<input type="radio" name="color" value="skin-{{ $color }}" style="display: none;">
<span class="btn bg-{{ $color }} btn-xs">
<i class="far fa-eye"></i>
</span>
</label>
</td>
</tr>
<tr>
<td>@lang('admin.customize.colors.'.$color.'-light')</td>
<td>
<label>
<input type="radio" name="color" value="skin-{{ $color }}-light" style="display: none;">
<span class="btn bg-{{ $color }} btn-xs">
<i class="far fa-eye"></i>
</span>
</label>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
<div class="box-footer">
<input type="submit" class="el-button el-button--primary" value="@lang('general.submit')" name="submit_color">
</div>
</form>
</div>
<div class="col-md-9">
{!! $forms['homepage']->render() !!}
{!! $forms['customJsCss']->render() !!}
</div>
</div>
</section><!-- /.content -->
</div><!-- /.content-wrapper -->
@endsection

View File

@ -0,0 +1,74 @@
{% extends 'admin.base' %}
{% block title %}{{ trans('general.customize') }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-3">
<form class="box box-primary" method="post" action="{{ url('/admin/customize?action=color') }}">
{{ csrf_field() }}
<div class="box-header with-border">
<h3 class="box-title">
{{ trans('admin.customize.change-color.title') }}
</h3>
</div>
<div class="box-body no-padding">
<table class="table table-striped bring-up nth-2-center" id="change-color">
<tbody>
{% for color in colors %}
<tr>
<td>{{ trans("admin.customize.colors.#{color}") }}</td>
<td>
<label>
<input
type="radio"
name="color"
value="skin-{{ color }}"
style="display: none;"
>
<span class="btn bg-{{ color }} btn-xs">
<i class="far fa-eye"></i>
</span>
</label>
</td>
</tr>
<tr>
<td>{{ trans("admin.customize.colors.#{color}-light") }}</td>
<td>
<label>
<input
type="radio"
name="color"
value="skin-{{ color }}-light"
style="display: none;"
>
<span class="btn bg-{{ color }} btn-xs">
<i class="far fa-eye"></i>
</span>
</label>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="box-footer">
<input
type="submit"
class="el-button el-button--primary"
value="{{ trans('general.submit') }}"
name="submit_color"
>
</div>
</form>
</div>
<div class="col-md-9">
{{ forms.homepage|raw }}
{{ forms.custom_js_css|raw }}
</div>
</div>
{% endblock %}
{% block before_foot %}
<link rel="stylesheet" href="{{ skins_css }}">
{% endblock %}

View File

@ -1,67 +0,0 @@
@extends('admin.master')
@section('title', trans('general.i18n'))
@section('content')
<div class="content-wrapper">
<section class="content-header">
<h1>@lang('general.i18n')</h1>
</section>
<section class="content">
<div class="row">
<div class="col-lg-8">
<div id="table"></div>
</div>
<div class="col-lg-4">
<form action="{{ url('/admin/i18n') }}" method="post">
<div class="box box-primary">
<div class="box-header">
<h3 class="box-title">@lang('admin.i18n.add')</h3>
</div>
<div class="box-body">
@if (session()->pull('success'))
<div class="callout callout-success">@lang('admin.i18n.added')</div>
@endif
@if ($errors->any())
<div class="callout callout-danger">{{ $errors->first() }}</div>
@endif
@csrf
<table class="table">
<tbody>
<tr>
<td>@lang('admin.i18n.group')</td>
<td>
<input type="text" class="form-control" name="group" required>
</td>
</tr>
<tr>
<td>@lang('admin.i18n.key')</td>
<td>
<input type="text" class="form-control" name="key" required>
</td>
</tr>
<tr>
<td>@lang('admin.i18n.text')</td>
<td>
<input type="text" class="form-control" name="text" required>
</td>
</tr>
</tbody>
</table>
</div>
<div class="box-footer">
<input type="submit" value="@lang('general.submit')" class="el-button el-button--primary">
</div>
</div>
</form>
<div class="callout callout-info">
<a href="https://blessing.netlify.com/ui-text.html" target="_blank">
@lang('admin.i18n.tip')
</a>
</div>
</div>
</div>
</section>
</div>
@endsection

View File

@ -0,0 +1,60 @@
{% extends 'admin.base' %}
{% block title %}{{ trans('general.i18n') }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-lg-8"><div id="table"></div></div>
<div class="col-lg-4">
<form action="{{ url('/admin/i18n') }}" method="post">
<div class="box box-primary">
<div class="box-header">
<h3 class="box-title">{{ trans('admin.i18n.add') }}</h3>
</div>
<div class="box-body">
{% if errors.any %}
<div class="callout callout-danger">{{ errors.first }}</div>
{% elseif session_pull('success') %}
<div class="callout callout-success">{{ trans('admin.i18n.added') }}</div>
{% endif %}
{{ csrf_field() }}
<table class="table">
<tbody>
<tr>
<td>{{ trans('admin.i18n.group') }}</td>
<td>
<input type="text" class="form-control" name="group" required>
</td>
</tr>
<tr>
<td>{{ trans('admin.i18n.key') }}</td>
<td>
<input type="text" class="form-control" name="key" required>
</td>
</tr>
<tr>
<td>{{ trans('admin.i18n.text') }}</td>
<td>
<input type="text" class="form-control" name="text" required>
</td>
</tr>
</tbody>
</table>
</div>
<div class="box-footer">
<input
type="submit"
value="{{ trans('general.submit') }}"
class="el-button el-button--primary"
>
</div>
</div>
</form>
<div class="callout callout-info">
<a href="https://blessing.netlify.com/ui-text.html" target="_blank">
{{ trans('admin.i18n.tip') }}
</a>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,145 +0,0 @@
@extends('admin.master')
@section('title', trans('general.dashboard'))
@section('content')
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
@lang('general.dashboard')
</h1>
</section>
<!-- Main content -->
<section class="content">
<div class="row">
<div class="col-md-6">
<div class="row">
<div class="col-md-6">
<div class="small-box bg-aqua">
<div class="inner">
<h3>{{ resolve(\App\Models\User::class)->count() }}</h3>
<p>@lang('admin.index.total-users')</p>
</div>
<div class="icon"><i class="fas fa-users"></i></div>
<a href="{{ url('admin/users') }}" class="small-box-footer">
@lang('general.user-manage') <i class="fa fa-arrow-circle-right"></i>
</a>
</div>
</div>
<div class="col-md-6">
<div class="small-box bg-green">
<div class="inner">
<h3>{{ App\Models\Player::count() }}</h3>
<p>@lang('admin.index.total-players')</p>
</div>
<div class="icon"><i class="fas fa-gamepad"></i></div>
<a href="{{ url('admin/players') }}" class="small-box-footer">
@lang('general.player-manage') <i class="fa fa-arrow-circle-right"></i>
</a>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="small-box bg-purple">
<div class="inner">
<h3>{{ App\Models\Texture::count() }}</h3>
<p>@lang('admin.index.total-textures')</p>
</div>
<div class="icon"><i class="fas fa-file"></i></div>
</div>
</div>
<div class="col-md-6">
<div class="small-box bg-yellow">
<div class="inner">
@php
$size = DB::table('textures')->sum('size') ?: 0;
@endphp
<h3>{{ $size > 1024 ? round($size / 1024, 1).'MB' : $size.'KB' }}</h3>
<p>@lang('admin.index.disk-usage')</p>
</div>
<div class="icon"><i class="fas fa-hdd"></i></div>
</div>
</div>
</div>
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">@lang('admin.notifications.send.title')</h3>
</div>
<form method="post" action="{{ url('/admin/notifications/send') }}">
@csrf
<div class="box-body">
@if ($errors->any())
<div class="callout callout-danger">{{ $errors->first() }}</div>
@endif
@if ($sentResult = Session::pull('sentResult'))
<div class="callout callout-success">{{ $sentResult }}</div>
@endif
<div class="form-group">
<label>@lang('admin.notifications.receiver.title')</label>
<div class="radio">
<label>
<input type="radio" name="receiver" value="all" required>
@lang('admin.notifications.receiver.all')
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="receiver" value="normal" required>
@lang('admin.notifications.receiver.normal')
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="receiver" value="uid" required>
@lang('admin.notifications.receiver.uid') &nbsp;
<input type="number" name="uid" class="form-control">
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="receiver" value="email" required>
@lang('admin.notifications.receiver.email') &nbsp;
<input type="email" name="email" class="form-control">
</label>
</div>
</div>
<div class="form-group">
<label>@lang('admin.notifications.title')</label>
<input type="text" name="title" class="form-control" required>
</div>
<div class="form-group">
<label>@lang('admin.notifications.content')</label>
<textarea name="content" class="form-control" rows="3"></textarea>
</div>
</div>
<div class="box-footer">
<input type="submit" value="@lang('general.submit')" class="el-button el-button--primary">
</div>
</form>
</div>
</div>
<div class="col-md-6">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">@lang('admin.index.overview')</h3>
</div>
<div class="box-body">
<div id="chart"></div>
</div>
</div>
</div>
</div>
</section><!-- /.content -->
</div><!-- /.content-wrapper -->
@endsection

View File

@ -0,0 +1,136 @@
{% extends 'admin.base' %}
{% block title %}{{ trans('general.dashboard') }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="row">
<div class="col-md-6">
<div class="small-box bg-aqua">
<div class="inner">
<h3>{{ sum.users }}</h3>
<p>{{ trans('admin.index.total-users') }}</p>
</div>
<div class="icon"><i class="fas fa-users"></i></div>
<a href="{{ url('admin/users') }}" class="small-box-footer">
{{ trans('general.user-manage') }}&nbsp;
<i class="fa fa-arrow-circle-right"></i>
</a>
</div>
</div>
<div class="col-md-6">
<div class="small-box bg-green">
<div class="inner">
<h3>{{ sum.players }}</h3>
<p>{{ trans('admin.index.total-players') }}</p>
</div>
<div class="icon"><i class="fas fa-gamepad"></i></div>
<a href="{{ url('admin/players') }}" class="small-box-footer">
{{ trans('general.player-manage') }}&nbsp;
<i class="fa fa-arrow-circle-right"></i>
</a>
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="small-box bg-purple">
<div class="inner">
<h3>{{ sum.textures }}</h3>
<p>{{ trans('admin.index.total-textures') }}</p>
</div>
<div class="icon"><i class="fas fa-file"></i></div>
</div>
</div>
<div class="col-md-6">
<div class="small-box bg-yellow">
<div class="inner">
{% if sum.storage > 1024 %}
<h3>{{ (sum.storage / 1024)|round(1) }}MB</h3>
{% else %}
<h3>{{ sum.storage }}KB</h3>
{% endif %}
<p>{{ trans('admin.index.disk-usage') }}</p>
</div>
<div class="icon"><i class="fas fa-hdd"></i></div>
</div>
</div>
</div>
<div class="box">
<div class="box-header with-border">
<h3 class="box-title">{{ trans('admin.notifications.send.title') }}</h3>
</div>
<form method="post" action="{{ url('/admin/notifications/send') }}">
{{ csrf_field() }}
<div class="box-body">
{% if errors.any %}
<div class="callout callout-danger">{{ errors.first }}</div>
{% endif %}
{% set sent_result = session_pull('sentResult') %}
{% if sent_result %}
<div class="callout callout-success">{{ sent_result }}</div>
{% endif %}
<div class="form-group">
<label>{{ trans('admin.notifications.receiver.title') }}</label>
<div class="radio">
<label>
<input type="radio" name="receiver" value="all" required>
{{ trans('admin.notifications.receiver.all') }}
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="receiver" value="normal" required>
{{ trans('admin.notifications.receiver.normal') }}
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="receiver" value="uid" required>
{{ trans('admin.notifications.receiver.uid') }} &nbsp;
<input type="number" name="uid" class="form-control">
</label>
</div>
<div class="radio">
<label>
<input type="radio" name="receiver" value="email" required>
{{ trans('admin.notifications.receiver.email') }} &nbsp;
<input type="email" name="email" class="form-control">
</label>
</div>
</div>
<div class="form-group">
<label>{{ trans('admin.notifications.title') }}</label>
<input type="text" name="title" class="form-control" required>
</div>
<div class="form-group">
<label>{{ trans('admin.notifications.content') }}</label>
<textarea name="content" class="form-control" rows="3"></textarea>
</div>
</div>
<div class="box-footer">
<input
type="submit"
value="{{ trans('general.submit') }}"
class="el-button el-button--primary"
>
</div>
</form>
</div>
</div>
<div class="col-md-6">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">{{ trans('admin.index.overview') }}</h3>
</div>
<div class="box-body"><div id="chart"></div></div>
</div>
</div>
</div>
{% endblock %}

View File

@ -1,2 +0,0 @@
@component('common.skeleton', ['parent' => 'admin', 'title' => trans('general.plugin-market')])
@endcomponent

View File

@ -0,0 +1,3 @@
{% extends 'admin.base' %}
{% block title %}{{ trans('general.plugin-market') }}{% endblock %}

View File

@ -1,94 +1,20 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@include('shared.head')
<title>@yield('title') - {{ option_localized('site_name') }}</title>
@include('common.favicon')
<!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="csrf-token" content="{{ csrf_token() }}">
@include('common.theme-color')
<!-- App Styles -->
@include('common.dependencies.style')
@yield('style')
</head>
@php
$user = auth()->user();
@endphp
<body class="hold-transition {{ option('color_scheme') }} sidebar-mini">
<div class="wrapper">
<!-- Main Header -->
<header class="main-header">
<!-- Logo -->
<a href="{{ option('site_url') }}" class="logo">
<!-- mini logo for sidebar mini 50x50 pixels -->
<span class="logo-mini"> <i class="fas fa-bookmark"></i> </span>
<!-- logo for regular state and mobile devices -->
<span class="logo-lg">{{ option_localized('site_name') }}</span>
</a>
<!-- Header Navbar -->
<nav class="navbar navbar-static-top" role="navigation">
<!-- Sidebar toggle button-->
<a href="#" class="sidebar-toggle" data-toggle="push-menu" role="button">
<i class="fas fa-bars"></i>
<span class="sr-only">Toggle navigation</span>
</a>
<!-- Navbar Right Menu -->
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
@include('common.notifications-menu')
@include('common.language')
@include('common.user-menu')
</ul>
</div>
</nav>
</header>
<!-- Left side column. contains the logo and sidebar -->
<aside class="main-sidebar">
<!-- sidebar: style can be found in sidebar.less -->
<section class="sidebar">
@include('common.user-panel')
<!-- Sidebar Menu -->
<ul class="sidebar-menu tree" data-widget="tree">
<li class="header">@lang('general.admin-panel')</li>
{!! bs_menu('admin') !!}
<li class="header">@lang('general.back')</li>
<li><a href="{{ url('user') }}"><i class="fas fa-user"></i> &nbsp;<span>@lang('general.user-center')</span></a></li>
</ul><!-- /.sidebar-menu -->
</section>
<!-- /.sidebar -->
</aside>
@include('shared.header')
@include('shared.sidebar', ['scope' => 'admin'])
@yield('content')
@include('shared.footer')
</div>
<!-- Main Footer -->
<footer class="main-footer">
<!-- YOU CAN NOT MODIFIY THE COPYRIGHT TEXT W/O PERMISSION -->
<div id="copyright-text" class="pull-right hidden-xs">
@include('common.copyright')
</div>
<!-- Default to the left -->
@include('common.custom-copyright')
</footer>
</div><!-- ./wrapper -->
<!-- App Scripts -->
@include('common.dependencies.script')
@if ($user->permission >= \App\Models\User::SUPER_ADMIN)
<script defer src="{{ webpack_assets('check-updates.js') }}"></script>
@endif
@include('shared.foot')
@yield('script')
</body>
</html>

View File

@ -1,37 +0,0 @@
@extends('admin.master')
@section('title', trans('general.options'))
@section('content')
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
@lang('general.options')
</h1>
</section>
<!-- Main content -->
<section class="content">
<div class="row">
<div class="col-md-6">
{!! $forms['general']->render() !!}
</div>
<div class="col-md-6">
{!! $forms['announ']->render() !!}
{!! $forms['meta']->render() !!}
{!! $forms['recaptcha']->render() !!}
</div>
</div>
</section><!-- /.content -->
</div><!-- /.content-wrapper -->
@endsection

View File

@ -0,0 +1,16 @@
{% extends 'admin.base' %}
{% block title %}{{ trans('general.options') }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
{{ forms.general.render()|raw }}
</div>
<div class="col-md-6">
{{ forms.announ.render()|raw }}
{{ forms.meta.render()|raw }}
{{ forms.recaptcha.render()|raw }}
</div>
</div>
{% endblock %}

View File

@ -1,2 +0,0 @@
@component('common.skeleton', ['parent' => 'admin', 'title' => trans('general.player-manage')])
@endcomponent

View File

@ -0,0 +1,3 @@
{% extends 'admin.base' %}
{% block title %}{{ trans('general.player-manage') }}{% endblock %}

View File

@ -1,2 +0,0 @@
@component('common.skeleton', ['parent' => 'admin', 'title' => trans('general.plugin-manage')])
@endcomponent

View File

@ -0,0 +1,3 @@
{% extends 'admin.base' %}
{% block title %}{{ trans('general.plugin-manage') }}{% endblock %}

View File

@ -1,2 +0,0 @@
@component('common.skeleton', ['parent' => 'admin', 'title' => trans('general.report-manage')])
@endcomponent

View File

@ -0,0 +1,3 @@
{% extends 'admin.base' %}
{% block title %}{{ trans('general.report-manage') }}{% endblock %}

View File

@ -1,41 +0,0 @@
@extends('admin.master')
@section('title', trans('general.res-options'))
@section('content')
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
@lang('general.res-options')
</h1>
</section>
<!-- Main content -->
<section class="content">
<div class="row">
<div class="col-md-12">
<div class="callout callout-warning">@lang('options.res-warning')</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
{!! $forms['resources']->render() !!}
{!! $forms['redis']->render() !!}
</div>
<div class="col-md-6">
{!! $forms['cache']->render() !!}
</div>
</div>
</section><!-- /.content -->
</div><!-- /.content-wrapper -->
@endsection

View File

@ -0,0 +1,23 @@
{% extends 'admin.base' %}
{% block title %}{{ trans('general.res-options') }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="callout callout-warning">
{{ trans('options.res-warning') }}
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
{{ forms.resources.render()|raw }}
{{ forms.redis.render()|raw }}
</div>
<div class="col-md-6">
{{ forms.cache.render()|raw }}
</div>
</div>
{% endblock %}

View File

@ -1,35 +0,0 @@
@extends('admin.master')
@section('title', trans('general.score-options'))
@section('content')
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
@lang('general.score-options')
</h1>
</section>
<!-- Main content -->
<section class="content">
<div class="row">
<div class="col-md-6">
{!! $forms['rate']->render() !!}
{!! $forms['report']->render() !!}
</div>
<div class="col-md-6">
{!! $forms['sign']->render() !!}
{!! $forms['sharing']->render() !!}
</div>
</div>
</section><!-- /.content -->
</div><!-- /.content-wrapper -->
@endsection

View File

@ -0,0 +1,16 @@
{% extends 'admin.base' %}
{% block title %}{{ trans('general.score-options') }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
{{ forms.rate.render()|raw }}
{{ forms.report.render()|raw }}
</div>
<div class="col-md-6">
{{ forms.sign.render()|raw }}
{{ forms.sharing.render()|raw }}
</div>
</div>
{% endblock %}

View File

@ -1,50 +0,0 @@
@extends('admin.master')
@section('title', trans('general.status'))
@section('content')
<div class="content-wrapper">
<section class="content-header">
<h1>@lang('general.status')</h1>
</section>
<section class="content">
<div class="row">
<div class="col-md-6">
<div class="box">
<div class="box-header">
<h3 class="box-title">@lang('admin.status.info')</h3>
</div>
<div class="box-body">
<table class="table table-bordered table-striped">
<tbody>
@foreach ($detail as $category => $info)
<tr>
<th colspan="2">@lang("admin.status.$category.name")</th>
</tr>
@foreach ($info as $key => $value)
<tr>
<td>@lang("admin.status.$category.$key")</td>
<td>{{ $value }}</td>
</tr>
@endforeach
@endforeach
<tr>
<th colspan="2">@lang('admin.status.plugins', ['amount' => $plugins->count()])</th>
</tr>
@foreach ($plugins as $plugin)
<tr>
<td>{{ $plugin['title'] }}</td>
<td>{{ $plugin['version'] }}</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-6"></div>
</div>
</section>
</div>
@endsection

View File

@ -0,0 +1,44 @@
{% extends 'admin.base' %}
{% block title %}{{ trans('general.status') }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="box">
<div class="box-header">
<h3 class="box-title">{{ trans('admin.status.info') }}</h3>
</div>
<div class="box-body">
<table class="table table-bordered table-striped">
<tbody>
{% for category, info in detail %}
<tr>
<th colspan="2">{{ trans("admin.status.#{category}.name") }}</th>
</tr>
{% for key, value in info %}
<tr>
<td>{{ trans("admin.status.#{category}.#{key}") }}</td>
<td>{{ value }}</td>
</tr>
{% endfor %}
{% endfor %}
<tr>
<th colspan="2">
{{ trans('admin.status.plugins', {amount: plugins|length}) }}
</th>
</tr>
{% for plugin in plugins %}
<tr>
<td>{{ plugin.title }}</td>
<td>{{ plugin.version }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-6"></div>
</div>
{% endblock %}

View File

@ -1,87 +0,0 @@
@extends('admin.master')
@section('title', trans('general.check-update'))
@section('content')
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
@lang('general.check-update')
</h1>
</section>
<!-- Main content -->
<section class="content">
<div class="row">
<div class="col-md-6">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">@lang('admin.update.info.title')</h3>
</div><!-- /.box-header -->
<div class="box-body">
@if ($extra['canUpdate'])
<div class="callout callout-info">@lang('admin.update.info.available')</div>
<table class="table">
<tbody>
<tr>
<td class="key">@lang('admin.update.info.versions.latest')</td>
<td class="value">
v{{ $info['latest'] }}
</td>
</tr>
<tr>
<td class="key">@lang('admin.update.info.versions.current')</td>
<td class="value">
v{{ $info['current'] }}
</td>
</tr>
</tbody>
</table>
@else
@if (is_string($error))
<div class="callout callout-danger">{{ trans('admin.update.errors.connection', ['error' => $error]) }}</div>
@else
<div class="callout callout-success">{{ trans('admin.update.info.up-to-date') }}</div>
@endif
<table class="table">
<tbody>
<tr>
<td class="key">@lang('admin.update.info.versions.current')</td>
<td class="value">
v{{ $info['current'] }}
</td>
</tr>
</tbody>
</table>
@endif
</div><!-- /.box-body -->
<div class="box-footer">
<span id="update-button"></span>
{!! trans('admin.update.info.check-github', ['url' => 'https://github.com/bs-community/blessing-skin-server/releases']) !!}
</div>
</div>
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">@lang('admin.update.cautions.title')</h3>
</div><!-- /.box-header -->
<div class="box-body">
{!! nl2p(trans('admin.update.cautions.text')) !!}
</div><!-- /.box-body -->
</div>
</div>
</div>
</section><!-- /.content -->
</div><!-- /.content-wrapper -->
<script>
blessing.extra = @json($extra)
</script>
@endsection

View File

@ -0,0 +1,91 @@
{% extends 'admin.base' %}
{% block title %}{{ trans('general.check-update') }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-6">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">{{ trans('admin.update.info.title') }}</h3>
</div>
<div class="box-body">
{% if extra.canUpdate %}
<div class="callout callout-info">
{{ trans('admin.update.info.available') }}
</div>
<table class="table">
<tbody>
<tr>
<td class="key">{{ trans('admin.update.info.versions.latest') }}</td>
<td class="value">
v{{ info.latest }}
</td>
</tr>
<tr>
<td class="key">{{ trans('admin.update.info.versions.current') }}</td>
<td class="value">
v{{ info.current }}
</td>
</tr>
</tbody>
</table>
{% else %}
{% if error is not empty %}
<div class="callout callout-danger">
{{ trans('admin.update.errors.connection', {error: error}) }}
</div>
{% else %}
<div class="callout callout-success">
{{ trans('admin.update.info.up-to-date') }}
</div>
{% endif %}
<table class="table">
<tbody>
<tr>
<td class="key">
{{ trans('admin.update.info.versions.current') }}</td>
<td class="value">
v{{ info.current }}
</td>
</tr>
</tbody>
</table>
{% endif %}
</div>
<div class="box-footer">
<span id="update-button"></span>
<a
target="_blank"
class="el-button pull-right"
href="https://github.com/bs-community/blessing-skin-server/releases"
>
{{ trans('admin.update.info.check-github') }}
</a>
</div>
</div>
<div class="box box-default">
<div class="box-header with-border">
<h3 class="box-title">
{{ trans('admin.update.cautions.title') }}
</h3>
</div>
<div class="box-body">
{% for text in trans('admin.update.cautions.text')|split('\n') %}
<p>{{ text }}</p>
{% endfor %}
<a target="_blank" href="https://blessing.netlify.com/update-sources.html">
{{ trans('admin.update.cautions.link') }}
</a>
</div>
</div>
</div>
</div>
{% endblock %}
{% block before_foot %}
<script>
blessing.extra = {{ extra|json_encode|raw }}
</script>
{% endblock %}

View File

@ -1,2 +0,0 @@
@component('common.skeleton', ['parent' => 'admin', 'title' => trans('general.user-manage')])
@endcomponent

View File

@ -0,0 +1,3 @@
{% extends 'admin.base' %}
{% block title %}{{ trans('general.user-manage') }}{% endblock %}

View File

@ -1,14 +0,0 @@
@extends("$parent.master")
@section('title', $title)
@section('content')
<div class="content-wrapper">
<section class="content-header">
<h1>{{ $title }}</h1>
</section>
<section class="content"></section>
</div>
{{ $bottom ?? '' }}
@endsection

View File

@ -1,16 +1,8 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@include('shared.head')
<title>{{ option_localized('site_name') }}</title>
@include('common.favicon')
<!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="csrf-token" content="{{ csrf_token() }}">
@include('common.seo-meta-tags')
<!-- App Styles -->
@include('common.dependencies.style')
<style>
.hp-wrapper {
@if (option('fixed_bg'))
@ -63,10 +55,10 @@
<!-- Navbar Right Menu -->
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
@include('common.language')
@include('shared.languages')
@auth
@include('common.user-menu')
@include('shared.user-menu')
@else {{-- Anonymous User --}}
<!-- User Account Menu -->
<li class="dropdown user user-menu">

View File

@ -0,0 +1,8 @@
{% for script in scripts %}
<script src="{{ script }}"></script>
{% endfor %}
<script>
{{ inline_js|striptags('<div><span><ul><li><p><a><img><i>')|raw }}
</script>
{{ extra_foot|join('\n')|raw }}

View File

@ -0,0 +1,20 @@
{% set repo = 'https://github.com/bs-community/blessing-skin-server' %}
<footer class="main-footer">
<!-- YOU CAN NOT MODIFIY THE COPYRIGHT TEXT W/O PERMISSION -->
<div id="copyright-text" class="pull-right hidden-xs">
{% if copyright == 0 %}
Powered with ❤ by <a href="{{ repo }}">Blessing Skin Server</a>.
{% elseif copyright == 1 %}
Powered by <a href="{{ repo }}">Blessing Skin Server</a>.
{% elseif copyright == 2 %}
Proudly powered by <a href="{{ repo }}">Blessing Skin Server</a>.
{% elseif copyright == 3 %}
由 <a href="{{ repo }}">Blessing Skin Server</a> 强力驱动。
{% else %}
自豪地采用 <a href="{{ repo }}">Blessing Skin Server</a>。
{% endif %}
</div>
<!-- Default to the left -->
{{ custom_copyright|raw }}
</footer>

View File

@ -0,0 +1,18 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="csrf-token" content="{{ csrf_token() }}">
<meta name="theme-color" content="{{ theme_color }}">
<meta name="keywords" content="{{ seo.keywords }}">
<meta name="description" content="{{ seo.description }}">
{{ seo.extra|striptags('<meta>')|raw }}
<link rel="shortcut icon" href="{{ favicon }}">
<link rel="icon" type="image/png" href="{{ favicon }}" sizes="192x192">
<link rel="apple-touch-icon" href="{{ favicon }}" sizes="180x180">
{% for style in styles %}
<link rel="stylesheet" href="{{ style }}">
{% endfor %}
<style>{{ inline_css|striptags }}</style>
{{ extra_head|join('\n')|raw }}

View File

@ -0,0 +1,26 @@
<header class="main-header">
<!-- Logo -->
<a href="{{ url('/') }}" class="logo">
<!-- mini logo for sidebar mini 50x50 pixels -->
<span class="logo-mini"> <i class="fas fa-bookmark"></i> </span>
<!-- logo for regular state and mobile devices -->
<span class="logo-lg">{{ site_name }}</span>
</a>
<!-- Header Navbar -->
<nav class="navbar navbar-static-top" role="navigation">
<!-- Sidebar toggle button-->
<a href="#" class="sidebar-toggle" data-toggle="push-menu" role="button">
<i class="fas fa-bars"></i>
<span class="sr-only">Toggle navigation</span>
</a>
<!-- Navbar Right Menu -->
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
{{ include('shared.notifications') }}
{{ include('shared.languages') }}
{{ include('shared.user-menu') }}
</ul>
</div>
</nav>
</header>

View File

@ -0,0 +1,14 @@
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fas fa-language" aria-hidden="true"></i>
<span class="description-text">{{ current }}</span>
<span class="caret"></span>
</a>
<ul class="dropdown-menu" role="menu" id="language-menu">
{% for lang in langs %}
<li class="locale">
<a href="{{ lang.url }}">{{ lang.name }}</a>
</li>
{% endfor %}
</ul>
</li>

View File

@ -0,0 +1,25 @@
<li class="dropdown notifications-menu">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
<i class="fas fa-bell"></i>
{% if amount > 0 %}
<span class="label label-warning notifications-counter">{{ amount }}</span>
{% endif %}
</a>
<ul class="dropdown-menu">
{% if amount == 0 %}
<li class="header text-center">{{ trans('user.no-unread') }}</li>
{% else %}
<li>
<ul class="menu notifications-list">
{% for notification in notifications %}
<li>
<a href="#" data-nid="{{ notification.id }}">
<i class="far fa-circle text-aqua"></i> {{ notification.data.title }}
</a>
</li>
{% endfor %}
</ul>
</li>
{% endif %}
</ul>
</li>

View File

@ -0,0 +1,27 @@
<li class="{{ item.classes|join(' ') }}">
{% if item.children %}
<a href="#">
<i class="fas {{ item.icon }}"></i> &nbsp;
<span>{{ trans(item.title) }}</span>
<span class="pull-right-container">
<i class="fas fa-angle-left pull-right"></i>
</span>
</a>
<ul class="treeview-menu">
{% for child in item.children %}
{{ include('shared.side-menu-item', {item: child}) }}
{% endfor %}
</ul>
{% else %}
<a
href="{{ url(item.link) }}"
{% if attribute(item, 'new-tab') %}
target="_blank"
rel="noopener noreferrer"
{% endif %}
>
<i class="{{ item.icon == 'fa-circle' ? 'far' : 'fas' }} {{ item.icon }}"></i>
&nbsp;<span>{{ trans(item.title) }}</span>
</a>
{% endif %}
</li>

View File

@ -0,0 +1,3 @@
{% for item in items %}
{{ include('shared.side-menu-item', {item: item}) }}
{% endfor %}

View File

@ -0,0 +1,44 @@
<aside class="main-sidebar">
<section class="sidebar">
{{ include('shared.user-panel') }}
<ul class="sidebar-menu tree" data-widget="tree">
{% if scope == 'user' %}
<li class="header">{{ trans('general.user-center') }}</li>
{{ include('shared.side-menu', {type: 'user'}) }}
<li class="header">{{ trans('general.explore') }}</li>
{{ include('shared.side-menu', {type: 'explore'}) }}
{% if auth_user().admin %}
<li class="header">{{ trans('general.manage') }}</li>
<li>
<a href="{{ url('admin') }}">
<i class="fas fa-cog"></i> &nbsp;
<span>{{ trans('general.admin-panel') }}</span>
</a>
</li>
{% endif %}
{% elseif scope == 'admin' %}
<li class="header">{{ trans('general.admin-panel') }}</li>
{{ include('shared.side-menu', {type: 'admin'}) }}
<li class="header">{{ trans('general.back') }}</li>
<li>
<a href="{{ url('user') }}">
<i class="fas fa-user"></i> &nbsp;
<span>{{ trans('general.user-center') }}</span>
</a>
</li>
<li>
<a href="{{ url('skinlib') }}">
<i class="fas fa-archive"></i> &nbsp;
<span>{{ trans('general.skinlib') }}</span>
</a>
</li>
{% endif %}
</ul>
</section>
</aside>

View File

@ -0,0 +1,47 @@
<li class="dropdown user user-menu">
<!-- Menu Toggle Button -->
<a href="#" class="dropdown-toggle" data-toggle="dropdown">
{% if tiny_avatar %}
<img src="{{ tiny_avatar }}" class="user-image" alt="User Image">
{% else %}
<i class="fas fa-user"></i>
{% endif %}
<!-- hidden-xs hides the username on small devices so only the image appears. -->
<span class="hidden-xs nickname">{{ user.nickname ?? user.email }}</span>
</a>
<ul class="dropdown-menu">
<li class="user-header">
<img src="{{ avatar }}" alt="User Image">
<p>{{ user.email }}</p>
</li>
{% if user.admin %}
<li class="user-body">
<div class="row">
<div class="col-xs-4 text-center">
<a href="{{ url('admin') }}">{{ trans('general.admin-panel') }}</a>
</div>
<div class="col-xs-4 text-center">
<a href="{{ url('admin/users') }}">{{ trans('general.user-manage') }}</a>
</div>
<div class="col-xs-4 text-center">
<a href="{{ url('admin/options') }}">{{ trans('general.options') }}</a>
</div>
</div>
</li>
{% endif %}
<!-- Menu Footer-->
<li class="user-footer">
<div class="pull-left">
<a href="{{ url('user') }}" class="btn btn-default btn-flat">
{{ trans('general.user-center') }}
</a>
</div>
<div class="pull-right">
<button id="logout-button" class="btn btn-default btn-flat">
{{ trans('general.logout') }}
</button>
</div>
</li>
</ul>
</li>

View File

@ -0,0 +1,20 @@
<div class="user-panel">
<div class="pull-left image">
<img src="{{ avatar }}" alt="User Image">
</div>
<div class="pull-left info">
<p class="nickname">{{ user.nickname ?? user.email }}</p>
<i class="fas fa-circle text-success"></i> {{ role }}
{% if badges|length == 1 %}
<small class="label bg-{{ badges[0][1] }}">{{ badges[0][0] }}</small>
{% endif %}
</div>
</div>
{% if badges|length > 1 %}
<div class="user-panel" style="padding-top: 0">
{% for badge in badges %}
<small class="label bg-{{ badge[1] }}">{{ badge[0] }}</small>
{% endfor %}
</div>
{% endif %}

View File

@ -1,18 +1,8 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@include('shared.head')
<title>@yield('title') - {{ option_localized('site_name') }}</title>
@include('common.favicon')
<!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="csrf-token" content="{{ csrf_token() }}">
@include('common.theme-color')
@include('common.seo-meta-tags')
<!-- App Styles -->
@include('common.dependencies.style')
@yield('style')
</head>

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
{{ include('shared.head') }}
<title>{% block title %}{% endblock %} - {{ site_name }}</title>
</head>
<body class="hold-transition {{ color_scheme }} sidebar-mini">
<div class="wrapper">
{{ include('shared.header') }}
{{ include('shared.sidebar', {scope: 'user'}) }}
<div class="content-wrapper">
<section class="content-header">
<h1>{{ block('title') }}</h1>
</section>
<section class="content">
{% block content %}{% endblock %}
</section>
</div>
{{ include('shared.footer') }}
</div>
{% block before_foot %}{% endblock %}
{{ include('shared.foot') }}
</body>
</html>

View File

@ -1,25 +0,0 @@
@extends('user.master')
@section('title', trans('general.my-closet'))
@section('content')
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>
@lang('general.my-closet')
</h1>
</section>
<!-- Main content -->
<section class="content"></section><!-- /.content -->
</div><!-- /.content-wrapper -->
<script>
Object.defineProperty(blessing, 'extra', {
get: () => Object.freeze(@json($extra))
})
</script>
@endsection

View File

@ -0,0 +1,11 @@
{% extends 'user.base' %}
{% block title %}{{ trans('general.my-closet') }}{% endblock %}
{% block before_foot %}
<script>
Object.defineProperty(blessing, 'extra', {
get: () => Object.freeze({{ extra|json_encode|raw }})
})
</script>
{% endblock %}

View File

@ -1,76 +0,0 @@
@extends('user.master')
@section('title', trans('general.dashboard'))
@section('content')
<!-- Content Wrapper. Contains page content -->
<div class="content-wrapper">
<!-- Content Header (Page header) -->
<section class="content-header">
<h1>@lang('general.dashboard')</h1>
</section>
<!-- Main content -->
<section class="content">
<div class="row">
<div class="col-md-7">
<div class="box" id="usage-box"></div><!-- /.box -->
</div><!-- /.col -->
<div class="col-md-5">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">@lang('user.announcement')</h3>
@if (auth()->user()->isAdmin())
<a href="{{ url('/admin/options') }}">
<i class="fas fa-edit"></i>
</a>
@endif
</div><!-- /.box-header -->
<div class="box-body">
{!! $announcement !!}
</div><!-- /.box-body -->
</div>
</div>
</div>
</section><!-- /.content -->
</div><!-- /.content-wrapper -->
<div id="modal-score-instruction" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
<h4 class="modal-title">@lang('user.score-intro.title')</h4>
</div>
<div class="modal-body">
{!! $scoreIntro !!}
<hr />
<div class="row">
<div class="col-md-4">
<p class="text-center">@lang('user.score-intro.rates.storage', ['score' => option('score_per_storage')])</p>
</div>
<div class="col-md-4">
<p class="text-center">@lang('user.score-intro.rates.player', ['score' => option('score_per_player')])</p>
</div>
<div class="col-md-4">
<p class="text-center">@lang('user.score-intro.rates.closet', ['score' => option('score_per_closet_item')])</p>
</div>
</div>
</div>
<div class="modal-footer">
<button class="el-button" data-dismiss="modal">@lang('general.close')</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<script>
Object.defineProperty(blessing, 'extra', {
get: () => Object.freeze(@json($extra))
})
</script>
@endsection

View File

@ -0,0 +1,68 @@
{% extends 'user.base' %}
{% block title %}{{ trans('general.dashboard') }}{% endblock %}
{% block content %}
<div class="row">
<div class="col-md-7">
<div class="box" id="usage-box">
<div class="box-header with-border"></div>
<div class="box-body"></div>
<div class="box-footer"></div>
</div>
</div>
<div class="col-md-5">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">{{ trans('user.announcement') }}</h3>
{% if auth_user().admin %}
<a href="{{ url('/admin/options') }}">
<i class="fas fa-edit"></i>
</a>
{% endif %}
</div>
<div class="box-body">
{{ announcement|raw }}
</div>
</div>
</div>
</div>
{% endblock %}
{% block before_foot %}
<div id="modal-score-instruction" class="modal fade" tabindex="-1" role="dialog">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title">{{ trans('user.score-intro.title') }}</h4>
</div>
<div class="modal-body">
{{ score_intro|nl2br }}
<hr />
<div class="row">
{% for key, value in rates %}
<div class="col-md-4">
<p class="text-center">
{{ trans("user.score-intro.rates.#{key}", {score: value}) }}
</p>
</div>
{% endfor %}
</div>
</div>
<div class="modal-footer">
<button class="el-button" data-dismiss="modal">{{ trans('general.close') }}</button>
</div>
</div>
</div>
</div>
<script>
Object.defineProperty(blessing, 'extra', {
get: () => Object.freeze({{ extra|json_encode|raw }})
})
</script>
{% endblock %}

View File

@ -1,97 +1,20 @@
<!DOCTYPE html>
<html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
@include('shared.head')
<title>@yield('title') - {{ option_localized('site_name') }}</title>
@include('common.favicon')
<!-- Tell the browser to be responsive to screen width -->
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<meta name="csrf-token" content="{{ csrf_token() }}">
@include('common.theme-color')
<!-- App Styles -->
@include('common.dependencies.style')
@yield('style')
</head>
@php
$user = auth()->user();
@endphp
<body class="hold-transition {{ option('color_scheme') }} sidebar-mini">
<div class="wrapper">
<!-- Main Header -->
<header class="main-header">
<!-- Logo -->
<a href="{{ option('site_url') }}" class="logo">
<!-- mini logo for sidebar mini 50x50 pixels -->
<span class="logo-mini"> <i class="fas fa-bookmark"></i> </span>
<!-- logo for regular state and mobile devices -->
<span class="logo-lg">{{ option_localized('site_name') }}</span>
</a>
<!-- Header Navbar -->
<nav class="navbar navbar-static-top" role="navigation">
<!-- Sidebar toggle button-->
<a href="#" class="sidebar-toggle" data-toggle="push-menu" role="button">
<i class="fas fa-bars"></i>
<span class="sr-only">Toggle navigation</span>
</a>
<!-- Navbar Right Menu -->
<div class="navbar-custom-menu">
<ul class="nav navbar-nav">
@include('common.notifications-menu')
@include('common.language')
@include('common.user-menu')
</ul>
</div>
</nav>
</header>
<!-- Left side column. contains the logo and sidebar -->
<aside class="main-sidebar">
<!-- sidebar: style can be found in sidebar.less -->
<section class="sidebar">
@include('common.user-panel')
<!-- Sidebar Menu -->
<ul class="sidebar-menu tree" data-widget="tree">
<li class="header">@lang('general.user-center')</li>
{!! bs_menu('user') !!}
<li class="header">@lang('general.explore')</li>
{!! bs_menu('explore') !!}
@admin($user)
<li class="header">@lang('general.manage')</li>
<li><a href="{{ url('admin') }}"><i class="fas fa-cog"></i> &nbsp;<span>@lang('general.admin-panel')</span></a></li>
@endadmin
</ul><!-- /.sidebar-menu -->
</section>
<!-- /.sidebar -->
</aside>
@include('shared.header')
@include('shared.sidebar', ['scope' => 'user'])
@yield('content')
@include('shared.footer')
</div>
<!-- Main Footer -->
<footer class="main-footer">
<!-- YOU CAN NOT MODIFIY THE COPYRIGHT TEXT W/O PERMISSION -->
<div id="copyright-text" class="pull-right hidden-xs">
@include('common.copyright')
</div>
<!-- Default to the left -->
@include('common.custom-copyright')
</footer>
</div><!-- ./wrapper -->
<!-- App Scripts -->
@include('common.dependencies.script')
@include('shared.foot')
@yield('script')
</body>
</html>

View File

@ -1,2 +0,0 @@
@component('common.skeleton', ['parent' => 'user', 'title' => trans('general.oauth-manage')])
@endcomponent

View File

@ -0,0 +1,3 @@
{% extends 'user.base' %}
{% block title %}{{ trans('general.oauth-manage') }}{% endblock %}

View File

@ -1,5 +0,0 @@
@component('common.skeleton', ['parent' => 'user', 'title' => trans('general.player-manage')])
@slot('bottom')
<script>blessing.extra = @json($extra)</script>
@endslot
@endcomponent

View File

@ -0,0 +1,7 @@
{% extends 'user.base' %}
{% block title %}{{ trans('general.player-manage') }}{% endblock %}
{% block before_foot %}
<script>blessing.extra = {{ extra|json_encode|raw }}</script>
{% endblock %}

View File

@ -1,10 +0,0 @@
@component('common.skeleton', ['parent' => 'user', 'title' => trans('general.profile')])
@slot('bottom')
<script>
Object.defineProperty(blessing, 'extra', {
configurable: false,
get: () => Object.freeze(@json($extra)),
})
</script>
@endslot
@endcomponent

View File

@ -0,0 +1,12 @@
{% extends 'user.base' %}
{% block title %}{{ trans('general.profile') }}{% endblock %}
{% block before_foot %}
<script>
Object.defineProperty(blessing, 'extra', {
configurable: false,
get: () => Object.freeze({{ extra|json_encode|raw }}),
})
</script>
{% endblock %}

View File

@ -1,2 +0,0 @@
@component('common.skeleton', ['parent' => 'user', 'title' => trans('general.my-reports')])
@endcomponent

View File

@ -0,0 +1,3 @@
{% extends 'user.base' %}
{% block title %}{{ trans('general.my-reports') }}{% endblock %}

View File

@ -114,7 +114,7 @@ Route::group(['prefix' => 'skinlib'], function () {
* Admin Panel
*/
Route::group(['middleware' => ['authorize', 'admin'], 'prefix' => 'admin'], function () {
Route::view('/', 'admin.index');
Route::get('/', 'AdminController@index');
Route::get('/chart', 'AdminController@chartData');
Route::post('/notifications/send', 'AdminController@sendNotification');

View File

@ -154,12 +154,12 @@ class AdminConfigurationsTest extends BrowserKitTestCase
$this->visit('/admin/options')
->type('kw', 'meta_keywords')
->type('desc', 'meta_description')
->type('<!-- nothing -->', 'meta_extras')
->type('<meta>', 'meta_extras')
->press('submit_meta');
$this->visit('/')
->see('<meta name="keywords" content="kw">')
->see('<meta name="description" content="desc">')
->see('<!-- nothing -->');
->see('<meta>');
$this->visit('/admin/options')
->type('key', 'recaptcha_sitekey')

View File

@ -0,0 +1,72 @@
<?php
namespace Tests;
use Event;
use App\Models\User;
use App\Services\Webpack;
use App\Services\Translations\JavaScript;
use Symfony\Component\DomCrawler\Crawler;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class FootComposerTest extends TestCase
{
use DatabaseTransactions;
public function testInjectJavaScript()
{
option([
'custom_js' => '"<div></div>"</script><h1 id=disallowed></h1><script>',
]);
$user = factory(User::class)->make();
$this->actingAs($user);
$this->get('/user')->assertSee('"<div></div>"');
$crawler = new Crawler($this->get('/user')->getContent());
$this->assertCount(0, $crawler->filter('#disallowed'));
$this->mock(JavaScript::class, function ($mock) {
$mock->shouldReceive('generate')
->with('en')
->twice()
->andReturn('en.js');
$mock->shouldReceive('plugin')
->with('en')
->twice()
->andReturn('en_plugin.js');
});
$this->mock(Webpack::class, function ($mock) {
$mock->shouldReceive('url')->with('style.css');
$mock->shouldReceive('url')->with('skins/skin-blue.min.css');
$mock->shouldReceive('url')
->with('check-updates.js')
->once()
->andReturn('check-updates.js');
$mock->shouldReceive('url')
->with('index.js')
->twice()
->andReturn('index.js');
});
$this->get('/user')
->assertSee('en.js')
->assertSee('en_plugin.js')
->assertSee('index.js')
->assertDontSee('check-updates.js');
$superAdmin = factory(User::class, 'superAdmin')->make();
$this->actingAs($superAdmin);
$this->get('/admin')->assertSee('check-updates.js');
}
public function testAddExtra()
{
Event::listen(\App\Events\RenderingFooter::class, function ($event) {
$event->contents[] = '<div id=appended></div>';
});
$user = factory(User::class)->make();
$this->actingAs($user);
$this->get('/user')->assertSee('<div id=appended></div>');
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace Tests;
use Event;
use Symfony\Component\DomCrawler\Crawler;
use Illuminate\Foundation\Testing\DatabaseTransactions;
class HeadComposerTest extends TestCase
{
use DatabaseTransactions;
public function testAddFavicon()
{
$this->get('/')->assertSee(config('options.favicon_url'));
option(['favicon_url' => '/a']);
$this->get('/')->assertSee(url('/a'));
option(['favicon_url' => 'http://example.com/icon']);
$this->get('/')->assertSee('http://example.com/icon');
}
public function testApplyThemeColor()
{
$crawler = new Crawler($this->get('/')->getContent());
$this->assertCount(1, $crawler->filter('meta[name="theme-color"]'));
}
public function testSeo()
{
option([
'meta_keywords' => 'kw',
'meta_description' => 'desc',
'meta_extras' => '<meta name=fake><div id=disallowed></div>'
]);
$crawler = new Crawler($this->get('/')->getContent());
$this->assertEquals(
'kw',
$crawler->filter('meta[name=keywords]')->attr('content')
);
$this->assertEquals(
'desc',
$crawler->filter('meta[name=description]')->attr('content')
);
$this->assertCount(1, $crawler->filter('meta[name=fake]'));
$this->assertCount(0, $crawler->filter('div#disallowed'));
}
public function testInjectStyles()
{
option(['custom_css' => 'div {} <style><div id=disallowed></div></style>']);
$this->get('/')->assertSee('div {}');
$crawler = new Crawler($this->get('/')->getContent());
$this->assertCount(0, $crawler->filter('div#disallowed'));
}
public function testAddExtra()
{
Event::listen(\App\Events\RenderingHeader::class, function ($event) {
$event->contents[] = '<meta name=appended>';
});
$this->get('/')->assertSee('<meta name=appended>');
}
}

View File

@ -0,0 +1,12 @@
<?php
namespace Tests;
class LanguagesMenuComposerTest extends TestCase
{
public function testCompose()
{
$this->get('/')->assertSee('?lang=en')->assertDontSee('en_US');
$this->get('/?key=value')->assertSee('?key=value&amp;lang=en');
}
}

View File

@ -0,0 +1,67 @@
<?php
namespace Tests;
use Event;
use App\Events;
use App\Models\User;
use App\Services\Plugin;
use App\Services\PluginManager;
use Symfony\Component\DomCrawler\Crawler;
class SideMenuComposerTest extends TestCase
{
public function testEvents()
{
Event::fake();
$admin = factory(User::class, 'admin')->make();
$this->actingAs($admin)->get('/user');
Event::assertDispatched(Events\ConfigureUserMenu::class);
Event::assertDispatched(Events\ConfigureExploreMenu::class);
$this->get('/admin');
Event::assertDispatched(Events\ConfigureAdminMenu::class);
}
public function testTransform()
{
$user = factory(User::class)->make();
$this->actingAs($user);
$crawler = new Crawler($this->get('/user/oauth/manage')->getContent());
$this->assertCount(1, $crawler->filter('aside .treeview'));
$this->assertCount(2, $crawler->filter('aside .active'));
}
public function testCollectPluginConfigs()
{
$this->mock(PluginManager::class, function ($mock) {
$mock->shouldReceive('getEnabledPlugins')
->with()
->twice()
->andReturn(
collect(),
collect([
new Plugin(resource_path(''), [
'config' => 'user/master.blade.php',
'title' => 'Fake',
'name' => 'fake',
])
])
);
});
$admin = factory(User::class, 'admin')->make();
$this->actingAs($admin)
->get('/admin')
->assertDontSee(trans('general.plugin-configs'));
$this->actingAs($admin)
->get('/admin')
->assertSee(trans('general.plugin-configs'))
->assertSee('fa-circle')
->assertSee('Fake')
->assertSee('admin/plugins/config/fake');
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Tests;
use App\Models\User;
class UserMenuComposerTest extends TestCase
{
public function testAvatar()
{
$user = factory(User::class)->make();
$this->actingAs($user)
->get('/user')
->assertSee(
url('avatar/128/'.base64_encode($user->email).'.png?tid='.$user->avatar)
);
}
public function testTinyAvatar()
{
$user = factory(User::class)->make();
$this->actingAs($user)
->get('/')
->assertSee(
url('avatar/25/'.base64_encode($user->email).'.png?tid='.$user->avatar)
);
$this->actingAs($user)
->get('/skinlib')
->assertSee(
url('avatar/25/'.base64_encode($user->email).'.png?tid='.$user->avatar)
);
$this->actingAs($user)
->get('/user')
->assertDontSee(
url('avatar/25/'.base64_encode($user->email).'.png?tid='.$user->avatar)
);
}
}

View File

@ -0,0 +1,33 @@
<?php
namespace Tests;
use Event;
use App\Models\User;
class UserPanelComposerTest extends TestCase
{
public function testRenderUser()
{
$user = factory(User::class)->make();
$this->actingAs($user);
$this->get('/user')
->assertSee(trans('admin.users.status.normal'))
->assertSee(
url('avatar/45/'.base64_encode($user->email).'.png?tid='.$user->avatar)
);
}
public function testBadges()
{
$user = factory(User::class)->make();
$this->actingAs($user);
Event::listen(\App\Events\RenderingBadges::class, function ($event) {
$event->badges[] = ['Pro', 'purple'];
});
$this->get('/user')->assertSee('<small class="label bg-purple">Pro</small>');
}
}

View File

@ -18,7 +18,8 @@ class HookTest extends TestCase
$this->actAs('normal')
->get('/user')
->assertSee('Link A')
->assertSee('/to/a" target="_blank"')
->assertSee('/to/a')
->assertSee('target="_blank"')
->assertSee('fa-book');
// Out of bound