Settings Dark mode

This commit is contained in:
MarceauKa 2019-08-29 16:29:38 +02:00
parent 0ae23728c0
commit ad4d061f4a
21 changed files with 306 additions and 52 deletions

View File

@ -6,7 +6,9 @@ use App\Exports\ChestsExport;
use App\Exports\LinksExport;
use App\Exports\StoriesExport;
use App\Http\Requests\ImportRequest;
use App\Http\Requests\StoreSettingsRequest;
use App\Services\Import;
use App\Services\Shaarli\Shaarli;
use App\Tag;
use Illuminate\Http\Request;
use Maatwebsite\Excel\Facades\Excel;
@ -105,4 +107,21 @@ class ManageController extends Controller
$format == 'csv' ? \Maatwebsite\Excel\Excel::CSV : \Maatwebsite\Excel\Excel::XLSX
);
}
public function settingsForm(Request $request)
{
return view('manage.settings')->with([
'page_title' => __('Settings'),
'settings' => app('shaarli')->getSettings(),
]);
}
public function settingsStore(StoreSettingsRequest $request, Shaarli $shaarli)
{
$validated = collect($request->validated());
$shaarli->setSettings($validated);
$this->flash(__('Settings updated!'), 'success');
return redirect()->back();
}
}

View File

@ -6,46 +6,12 @@ use Closure;
class CheckForGlobalPrivacy
{
/** @var array $except */
private $except = [
'login',
'password/*',
];
public function handle($request, Closure $next)
{
$user = null;
foreach (['web', 'api'] as $guard) {
if (! $user) {
$user = auth($guard)->user();
}
}
if ($user || false === $this->globalPrivacyEnabled() || $this->inExceptArray($request)) {
if (app('shaarli')->authorizeFromRequest($request)) {
return $next($request);
}
return redirect()->route('login');
}
protected function globalPrivacyEnabled(): bool
{
return config('app.private', false);
}
protected function inExceptArray($request): bool
{
foreach ($this->except as $except) {
if ($except !== '/') {
$except = trim($except, '/');
}
if ($request->fullUrlIs($except) || $request->is($except)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,32 @@
<?php
namespace App\Http\Requests;
use Illuminate\Foundation\Http\FormRequest;
class StoreSettingsRequest extends FormRequest
{
public function authorize()
{
return auth()->check();
}
public function rules()
{
return [
'name' => [
'required',
'min:2',
'max:100',
],
'is_private' => [
'nullable',
'in:on,off',
],
'is_dark' => [
'nullable',
'in:on,off',
]
];
}
}

View File

@ -2,6 +2,7 @@
namespace App\Providers;
use App\Services\Shaarli\Shaarli;
use Illuminate\Support\Facades\View;
use Illuminate\Support\ServiceProvider;
@ -12,6 +13,12 @@ class AppServiceProvider extends ServiceProvider
if ($this->app->environment('production')) {
$this->app['request']->server->set('HTTPS', true);
}
$this->app->singleton(Shaarli::class, function ($app) {
return new Shaarli($app);
});
$this->app->alias(Shaarli::class, 'shaarli');
}
public function boot()

View File

@ -27,7 +27,7 @@ class AuthServiceProvider extends ServiceProvider
$this->registerPolicies();
Gate::define('restricted', function (?User $user) {
if (config('app.private') === false) {
if (app('shaarli')->getIsPrivate() === false) {
return true;
}

View File

@ -0,0 +1,106 @@
<?php
namespace App\Services\Shaarli;
use Illuminate\Foundation\Application;
use Illuminate\Http\Request;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Spatie\Valuestore\Valuestore;
/**
* @method string getName()
* @method bool getIsPrivate()
*/
class Shaarli
{
/** @var string VERSION */
public const VERSION = '1.0.0';
/** @var Application $app */
protected $app;
/** @var Valuestore $settings */
protected $settings;
public function __construct(Application $app)
{
$this->app = $app;
$this->settings = Valuestore::make(storage_path('settings.json'));
foreach ($this->app['config']->get('shaarli') as $key => $item) {
if ($this->settings->has($key) === false) {
$this->settings->put($key, $item);
}
}
}
public function authorizeFromRequest(Request $request): bool
{
$user = null;
if ($this->getIsPrivate() === false) {
return true;
}
foreach (['web', 'api'] as $guard) {
if (! $user) {
$user = auth($guard)->user();
}
}
if (! empty($user) || $this->requestAuthorizedForGlobalPrivacy($request)) {
return true;
}
return false;
}
public function requestAuthorizedForGlobalPrivacy(Request $request): bool
{
$excepts = [
'login',
'password/*',
];
foreach ($excepts as $except) {
if ($except !== '/') {
$except = trim($except, '/');
}
if ($request->fullUrlIs($except) || $request->is($except)) {
return true;
}
}
return false;
}
public function getSettings(): array
{
return $this->settings->all();
}
public function setSettings(Collection $settings): void
{
$this->settings->put('name', $settings->get('name'));
$this->settings->put('is_private', $settings->get('is_private') == 'on');
$this->settings->put('is_dark', $settings->get('is_dark') == 'on');
}
public function __call($name, $arguments)
{
if (substr($name, 0, 3) === 'get') {
$key = Str::snake(substr($name, 3));
if ($this->settings->has($key)) {
return $this->settings->get($key);
}
if (array_key_exists($key, config('shaarli'))) {
$this->settings->put($key, config('shaarli')[$key]);
return $this->settings->get($key);
}
}
throw new \BadMethodCallException("Method {$name} does not exists.");
}
}

View File

@ -17,6 +17,7 @@
"laravel/tinker": "^1.0",
"maatwebsite/excel": "^3.1",
"spatie/laravel-feed": "^2.3",
"spatie/valuestore": "^1.2",
"teamtnt/laravel-scout-tntsearch-driver": "^7.1"
},
"require-dev": {

57
composer.lock generated
View File

@ -1,10 +1,10 @@
{
"_readme": [
"This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "47e960a2a94937000a0a01f6dcc47e10",
"content-hash": "69dc31c906fede8ca4998f7d3ce078f8",
"packages": [
{
"name": "dnoegel/php-xdg-base-dir",
@ -2211,6 +2211,59 @@
],
"time": "2019-08-22T07:12:25+00:00"
},
{
"name": "spatie/valuestore",
"version": "1.2.3",
"source": {
"type": "git",
"url": "https://github.com/spatie/valuestore.git",
"reference": "798897f7d571aa0a62786ae531d573d3c6af55d0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/spatie/valuestore/zipball/798897f7d571aa0a62786ae531d573d3c6af55d0",
"reference": "798897f7d571aa0a62786ae531d573d3c6af55d0",
"shasum": ""
},
"require": {
"php": "^7.2"
},
"require-dev": {
"phpunit/phpunit": "^8.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Spatie\\Valuestore\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Freek Van der Herten",
"role": "Developer",
"email": "freek@spatie.be",
"homepage": "https://spatie.be"
},
{
"name": "Jolita Grazyte",
"role": "Developer",
"email": "jolita@spatie.be",
"homepage": "https://spatie.be"
}
],
"description": "Easily store some values",
"homepage": "https://github.com/spatie/valuestore",
"keywords": [
"json",
"spatie",
"valuestore"
],
"time": "2019-02-15T10:56:05+00:00"
},
{
"name": "swiftmailer/swiftmailer",
"version": "v6.2.1",

View File

@ -2,7 +2,6 @@
return [
'name' => env('APP_NAME', 'Laravel Shaarli'),
'private' => env('APP_PRIVATE', false),
'env' => env('APP_ENV', 'production'),
'debug' => env('APP_DEBUG', false),
'url' => env('APP_URL', 'http://localhost'),

7
config/shaarli.php Normal file
View File

@ -0,0 +1,7 @@
<?php
return [
'name' => env('APP_NAME'),
'is_private' => false,
'is_dark' => false,
];

View File

@ -2,10 +2,11 @@
It's a **free and open source platform** to host by yourself.
**Shaarli** allows you to **save your web links** (websites, youtube videos, ...), to **share your stories** and **manage your web accounts**.
All of your content can be **private or public** and can be browsed by **tags** or **all-in-one search**.
**Shaarli** allows you to **save your web links** (websites, youtube videos, ...), to **share your stories** and
**manage your web accounts**. All of your content can be **private or public** and can be browsed by **tags** or **all-in-one search**.
It's ready to use for **production**. **Laravel Shaarli** is inspired by [Shaarli](https://github.com/shaarli/Shaarli) but built with [Laravel](https://github.com/laravel/laravel).
It's ready to use for **production**. **Laravel Shaarli** is inspired by [Shaarli](https://github.com/shaarli/Shaarli)
but built with [Laravel](https://github.com/laravel/laravel) and [Vue.js](https://vuejs.org/).
## Requirements
@ -24,7 +25,7 @@ It's ready to use for **production**. **Laravel Shaarli** is inspired by [Shaarl
- [x] Original Shaarli import
- [x] RSS feed
- [x] Export
- [x] i18n
- [x] i18n (english and french)
## Screenshots

View File

@ -64,7 +64,6 @@
"Contents": "Contenus",
"Session": "Session",
"Account": "Compte",
"Data": "Données",
"Import": "Importer",
"Export": "Exporter",
"More": "Plus",
@ -115,6 +114,11 @@
"Width": "Largeur",
"Height": "Hauteur",
"Share button": "Bouton d'ajout rapide",
"Settings": "Paramètres",
"Site name": "Nom du site",
"Private content (all content is private and login is required)": "Contenu privé (tout est privé et nécessite d'être connecté)",
"Dark mode": "Mode sombre",
"Settings updated!": "Paramètres mis à jour !",
"Update account": "Modifier le compte",
"Your account has been updated!": "Votre compte a été mis à jour !",
"Update password": "Modifier le mot de passe",

10
resources/sass/_dark.scss vendored Normal file
View File

@ -0,0 +1,10 @@
body.dark {
background-image: linear-gradient(to top, #505285 0%, #585e92 12%, #65689f 25%, #7474b0 37%, #7e7ebb 50%, #8389c7 62%, #9795d4 75%, #a2a1dc 87%, #b5aee4 100%);
filter: hue-rotate(180deg) invert(1);
img,
video,
iframe {
filter: hue-rotate(180deg) invert(1);
}
}

View File

@ -3,6 +3,7 @@
@import '~bootstrap/scss/bootstrap';
@import '~vue-multiselect/dist/vue-multiselect.min.css';
@import '~mavon-editor/dist/css/index.css';
@import "dark";
html,
body {
@ -11,7 +12,7 @@ body {
}
body {
background-image: linear-gradient(to top, #e6e9f0 0%, #eef1f5 100%);
background-image: linear-gradient(to top, #dad4ec 0%, #dad4ec 1%, #f3e7e9 100%);
background-repeat: no-repeat;
background-size: cover;
background-attachment: fixed;
@ -116,3 +117,4 @@ nav.navbar,
}
}
}

View File

@ -3,7 +3,7 @@
<head>
@include('layouts.partials.head')
</head>
<body>
<body class="{{ app('shaarli')->getIsDark() ? 'dark' : 'light' }}">
<div id="app">
@include('layouts.partials.navbar')
<main class="py-4">

View File

@ -3,14 +3,17 @@
<head>
@include('layouts.partials.head')
</head>
<body>
<body class="{{ app('shaarli')->getIsDark() ? 'dark' : 'light' }}">
<div id="app">
@include('layouts.partials.navbar')
<main class="py-4">
<div class="container">
<div class="row">
<div class="col-12 col-md-4">
<div class="col-12 col-md-4 mb-3">
<div class="list-group">
<a href="{{ route('manage.settings') }}"
class="list-group-item list-group-item-action{{ request()->is('manage/settings') ? ' active' : '' }}"
>{{ __('Settings') }}</a>
<a href="{{ route('manage.tags') }}"
class="list-group-item list-group-item-action{{ request()->is('manage/tags') ? ' active' : '' }}"
>{{ __('Tags') }}</a>

View File

@ -1,6 +1,6 @@
<footer>
<p class="text-center">
{{ config('app.name') }} -
{{ config('app.name') }} - v{{ app('shaarli')::VERSION }} -
<a href="{{ route('feeds.main') }}">{{ __('RSS Feed') }}</a> -
<a href="https://github.com/MarceauKa/laravel-shaarli">{{ __('Source code') }}</a>
</p>

View File

@ -1,7 +1,7 @@
<nav class="navbar navbar-expand-md navbar-light mb-3">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel Shaarli') }}
{{ app('shaarli')->getName() }}
</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
<span class="navbar-toggler-icon"></span>
@ -32,7 +32,7 @@
<h6 class="dropdown-header">{{ __('Manage') }}</h6>
<a class="dropdown-item" href="{{ route('account') }}">{{ __('Account') }}</a>
<a class="dropdown-item" href="{{ route('manage.tags') }}">{{ __('Data') }}</a>
<a class="dropdown-item" href="{{ route('manage.settings') }}">{{ __('Settings') }}</a>
<h6 class="dropdown-header">{{ __('Session') }}</h6>

View File

@ -0,0 +1,41 @@
@extends('layouts.manage')
@section('content')
<div class="row justify-content-center">
<div class="col-12">
<div class="card">
<div class="card-header">{{ __('Settings') }}</div>
<div class="card-body">
<form method="POST" action="{{ route('manage.settings') }}" method="POST">
@csrf
<div class="form-group">
<label for="name">{{ __('Site name') }}</label>
<input type="text" class="form-control {{ $errors->has('name') ? ' is-invalid' : '' }}" name="name" id="name" value="{{ $settings['name'] }}">
@error('name')
<span class="invalid-feedback" role="alert">{{ $message }}</span>
@enderror
</div>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" name="is_private" id="is_private" {{ $settings['is_private'] ? ' checked' : '' }}>
<label class="custom-control-label" for="is_private">{{ __('Private content (all content is private and login is required)') }}</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" name="is_dark" id="is_dark" {{ $settings['is_dark'] ? ' checked' : '' }}>
<label class="custom-control-label" for="is_dark">{{ __('Dark mode') }}</label>
</div>
</div>
<button type="submit" class="btn btn-primary">{{ __('Save') }}</button>
</form>
</div>
</div>
</div>
</div>
@endsection

View File

@ -37,6 +37,8 @@ Route::post('account/password', 'AccountController@storePassword')->name('accoun
Route::get('manage/import', 'ManageController@importForm')->name('manage.import');
Route::post('manage/import', 'ManageController@importStore');
Route::get('manage/export', 'ManageController@exportForm')->name('manage.export');
Route::post('manage/export', 'ManageController@export')->name('manage.export');
Route::post('manage/export', 'ManageController@export');
Route::get('manage/tags', 'ManageController@tags')->name('manage.tags');
Route::get('manage/tags/delete/{tag}/{hash}', 'ManageController@deleteTag')->name('manage.tags.delete');
Route::get('manage/settings', 'ManageController@settingsForm')->name('manage.settings');
Route::post('manage/settings', 'ManageController@settingsStore');

1
storage/settings.json Normal file
View File

@ -0,0 +1 @@
{"is_private":false,"name":"Shaarli","is_dark":false}