Manage tags

This commit is contained in:
MarceauKa 2019-08-27 16:35:48 +02:00
parent 83a1e1c55d
commit 2a71485c5d
13 changed files with 264 additions and 152 deletions

View File

@ -4,21 +4,49 @@ namespace App\Http\Controllers;
use App\Http\Requests\ImportRequest; use App\Http\Requests\ImportRequest;
use App\Services\Import; use App\Services\Import;
use App\Tag;
use Illuminate\Http\Request; use Illuminate\Http\Request;
class ImportController extends Controller class ManageController extends Controller
{ {
public function __construct() public function __construct()
{ {
$this->middleware('auth'); $this->middleware('auth');
} }
public function form(Request $request) public function tags()
{ {
return view('import')->with(['page_title' => 'Importer']); $tags = Tag::withCount('posts')
->orderByDesc('posts_count')
->get();
return view('manage.tags')->with([
'page_title' => __('Tags'),
'tags' => $tags,
]);
} }
public function store(ImportRequest $request) public function deleteTag(Request $request, string $tag, string $hash)
{
if ($hash != csrf_token()) {
abort(403);
}
$tag = Tag::findNamedOrCreate($tag);
$tag->delete();
$this->flash("Le tag \"{$tag->name}\" a été supprimé !", 'success');
return redirect()->back();
}
public function importForm(Request $request)
{
return view('manage.import')->with([
'page_title' => __('Import')
]);
}
public function importStore(ImportRequest $request)
{ {
try { try {
$import = new Import( $import = new Import(

View File

@ -46,6 +46,7 @@
"Whoops!": "Oups !", "Whoops!": "Oups !",
"Whoops, something went wrong on our servers.": "Oups, quelque chose s'est mal passé sur nos serveurs.", "Whoops, something went wrong on our servers.": "Oups, quelque chose s'est mal passé sur nos serveurs.",
"Manage": "Gestion", "Manage": "Gestion",
"Tags": "Tags",
"Link": "Lien", "Link": "Lien",
"Story": "Story", "Story": "Story",
"Chest": "Coffre", "Chest": "Coffre",

View File

@ -23,6 +23,10 @@ nav.navbar,
box-shadow: $box-shadow-sm; box-shadow: $box-shadow-sm;
} }
.card {
box-shadow: $box-shadow-sm;
}
#app { #app {
display: flex; display: flex;
flex-direction: column; flex-direction: column;

View File

@ -1,68 +0,0 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Import') }}</div>
<div class="card-body">
<div class="alert alert-warning">
Attention, l'import a été testé avec la version <code>0.0.41</code> de Shaarli. Faites une sauvegarde avant toute tentative.
</div>
<p class="card-text">
Vous pouvez importer votre contenu depuis le <strong>Shaarli originel</strong>. Importez le fichier <code>data/datastore.php</code> (par défaut)
et vérifiez qu'il commence par <code>&lt;?php /* </code> et se termine par <code>*/ ?&gt;</code>.
</p>
</div>
<div class="card-footer">
<form method="POST" action="{{ route('import') }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="form-group">
<label for="file">Fichier d'import</label>
<input type="file" class="form-control-file {{ $errors->has('file') ? ' is-invalid' : '' }}" name="file" id="file" accept=".php">
@error('file')
<span class="invalid-feedback" role="alert">{{ $message }}</span>
@enderror
</div>
<div class="row">
<div class="col-12 col-md-6">
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" name="ignore_tags" id="ignore_tags">
<label class="custom-control-label" for="ignore_tags">Ignorer les tags ?</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" name="get_extras" id="get_extras">
<label class="custom-control-label" for="get_extras">Chercher les extras ?</label>
</div>
</div>
</div>
<div class="col-12 col-md-6">
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" name="refresh_search" id="refresh_search">
<label class="custom-control-label" for="refresh_search">Actualiser recherche ?</label>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">{{ __('Importer') }}</button>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -1,89 +1,16 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <html lang="{{ app()->getLocale() }}">
<head> <head>
<meta charset="utf-8"> @include('layouts.partials.head')
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@if(isset($page_title)){{ $page_title }} - @endif{{ config('app.name') }}</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
@if(auth()->check())
<meta name="api-token" content="{{ auth()->user()->api_token }}">
@endif
@stack('meta')
@include('feed::links')
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
<link href="{{ mix('css/app.css') }}" rel="stylesheet">
@stack('css')
</head> </head>
<body> <body>
<div id="app"> <div id="app">
<nav class="navbar navbar-expand-md navbar-light mb-3"> @include('layouts.partials.navbar')
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel Shaarli') }}
</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>
</button>
@can('restricted')
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<search url="{{ route('api.search') }}" id="search"></search>
<ul class="navbar-nav">
@guest
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
</li>
@else
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth::user()->name }} <span class="caret"></span>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<h6 class="dropdown-header">{{ __('Contents') }}</h6>
<a class="dropdown-item" href="{{ route('link.create') }}">{{ __('Add link') }}</a>
<a class="dropdown-item" href="{{ route('story.create') }}">{{ __('Add story') }}</a>
<a class="dropdown-item" href="{{ route('chest.create') }}">{{ __('Add chest') }}</a>
<h6 class="dropdown-header">{{ __('Manage') }}</h6>
<a class="dropdown-item" href="{{ route('account') }}">{{ __('Compte') }}</a>
<a class="dropdown-item" href="{{ route('import') }}">{{ __('Import') }}</a>
<h6 class="dropdown-header">{{ __('Session') }}</h6>
<a class="dropdown-item" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
@csrf
</form>
</div>
</li>
@endguest
</ul>
</div>
@endcan
</div>
</nav>
<main class="py-4"> <main class="py-4">
@yield('content') @yield('content')
</main> </main>
@include('layouts.partials.footer')
<footer>
<p class="text-center">
{{ config('app.name') }} -
<a href="{{ route('feeds.main') }}">{{ __('RSS Feed') }}</a> -
<a href="https://github.com/MarceauKa/laravel-shaarli">{{ __('Source code') }}</a>
</p>
</footer>
@include('partials.flash')
</div> </div>
@include('layouts.partials.scripts')
<script src="{{ mix('js/app.js') }}" defer></script>
@stack('js')
</body> </body>
</html> </html>

View File

@ -0,0 +1,32 @@
<!DOCTYPE html>
<html lang="{{ app()->getLocale() }}">
<head>
@include('layouts.partials.head')
</head>
<body>
<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="list-group">
<a href="{{ route('manage.import') }}"
class="list-group-item list-group-item-action{{ request()->is('manage/import') ? ' active' : '' }}"
>{{ __('Import') }}</a>
<a href="{{ route('manage.tags') }}"
class="list-group-item list-group-item-action{{ request()->is('manage/tags') ? ' active' : '' }}"
>{{ __('Tags') }}</a>
</div>
</div>
<div class="col-12 col-md-8">
@yield('content')
</div>
</div>
</div>
</main>
@include('layouts.partials.footer')
</div>
@include('layouts.partials.scripts')
</body>
</html>

View File

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

View File

@ -0,0 +1,13 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>@if(isset($page_title)){{ $page_title }} - @endif{{ config('app.name') }}</title>
<meta name="csrf-token" content="{{ csrf_token() }}">
@if(auth()->check())
<meta name="api-token" content="{{ auth()->user()->api_token }}">
@endif
@stack('meta')
@include('feed::links')
<link rel="dns-prefetch" href="//fonts.gstatic.com">
<link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet">
<link href="{{ mix('css/app.css') }}" rel="stylesheet">
@stack('css')

View File

@ -0,0 +1,55 @@
<nav class="navbar navbar-expand-md navbar-light mb-3">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
{{ config('app.name', 'Laravel Shaarli') }}
</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>
</button>
@can('restricted')
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<search url="{{ route('api.search') }}" id="search"></search>
<ul class="navbar-nav">
@guest
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
</li>
@else
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth::user()->name }} <span class="caret"></span>
</a>
<div class="dropdown-menu dropdown-menu-right" aria-labelledby="navbarDropdown">
<h6 class="dropdown-header">{{ __('Contents') }}</h6>
<a class="dropdown-item" href="{{ route('link.create') }}">{{ __('Add link') }}</a>
<a class="dropdown-item" href="{{ route('story.create') }}">{{ __('Add story') }}</a>
<a class="dropdown-item" href="{{ route('chest.create') }}">{{ __('Add chest') }}</a>
<h6 class="dropdown-header">{{ __('Manage') }}</h6>
<a class="dropdown-item" href="{{ route('account') }}">{{ __('Compte') }}</a>
<a class="dropdown-item" href="{{ route('manage.tags') }}">{{ __('Données') }}</a>
<h6 class="dropdown-header">{{ __('Session') }}</h6>
<a class="dropdown-item" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" style="display: none;">
@csrf
</form>
</div>
</li>
@endguest
</ul>
</div>
@endcan
</div>
</nav>

View File

@ -0,0 +1,2 @@
<script src="{{ mix('js/app.js') }}" defer></script>
@stack('js')

View File

@ -0,0 +1,66 @@
@extends('layouts.manage')
@section('content')
<div class="row justify-content-center">
<div class="col-12">
<div class="card">
<div class="card-header">{{ __('Import') }}</div>
<div class="card-body">
<div class="alert alert-warning">
Attention, l'import a été testé avec la version <code>0.0.41</code> de Shaarli. Faites une sauvegarde avant toute tentative.
</div>
<p class="card-text">
Vous pouvez importer votre contenu depuis le <strong>Shaarli originel</strong>. Importez le fichier <code>data/datastore.php</code> (par défaut)
et vérifiez qu'il commence par <code>&lt;?php /* </code> et se termine par <code>*/ ?&gt;</code>.
</p>
</div>
<div class="card-footer">
<form method="POST" action="{{ route('manage.import') }}" method="POST" enctype="multipart/form-data">
@csrf
<div class="form-group">
<label for="file">Fichier d'import</label>
<input type="file" class="form-control-file {{ $errors->has('file') ? ' is-invalid' : '' }}" name="file" id="file" accept=".php">
@error('file')
<span class="invalid-feedback" role="alert">{{ $message }}</span>
@enderror
</div>
<div class="row">
<div class="col-12 col-md-6">
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" name="ignore_tags" id="ignore_tags">
<label class="custom-control-label" for="ignore_tags">Ignorer les tags ?</label>
</div>
</div>
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" name="get_extras" id="get_extras">
<label class="custom-control-label" for="get_extras">Chercher les extras ?</label>
</div>
</div>
</div>
<div class="col-12 col-md-6">
<div class="form-group">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" name="refresh_search" id="refresh_search">
<label class="custom-control-label" for="refresh_search">Actualiser recherche ?</label>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-primary">{{ __('Importer') }}</button>
</form>
</div>
</div>
</div>
</div>
@endsection

View File

@ -0,0 +1,40 @@
@extends('layouts.manage')
@section('content')
<div class="row justify-content-center">
<div class="col-12">
<div class="card">
<div class="card-header">{{ __('Import') }}</div>
<div class="card-body">
@if($tags->isEmpty())
<div class="alert alert-info">Aucun tag.</div>
@else
<table class="table">
<thead>
<tr>
<td>Nom</td>
<td>Posts</td>
<td class="text-right">#</td>
</tr>
</thead>
<tbody>
@foreach($tags as $tag)
<tr>
<td>
<a href="{{ $tag->url }}">{{ $tag->name }}</a>
</td>
<td>{{ $tag->posts_count }}</td>
<td class="text-right">
<a href="{{ route('manage.tags.delete', [$tag->name, csrf_token()]) }}" class="btn btn-danger btn-sm">Supprimer</a>
</td>
</tr>
@endforeach
</tbody>
</table>
@endif
</div>
</div>
</div>
</div>
@endsection

View File

@ -33,5 +33,8 @@ Route::get('chest/{chest}', 'BrowseController@chest')->name('chest.view');
Route::get('account', 'AccountController@form')->name('account'); Route::get('account', 'AccountController@form')->name('account');
Route::post('account', 'AccountController@store'); Route::post('account', 'AccountController@store');
Route::post('account/password', 'AccountController@storePassword')->name('account.password'); Route::post('account/password', 'AccountController@storePassword')->name('account.password');
Route::get('import', 'ImportController@form')->name('import');
Route::post('import', 'ImportController@store'); Route::get('manage/import', 'ManageController@importForm')->name('manage.import');
Route::post('manage/import', 'ManageController@importStore');
Route::get('manage/tags', 'ManageController@tags')->name('manage.tags');
Route::get('manage/tags/delete/{tag}/{hash}', 'ManageController@deleteTag')->name('manage.tags.delete');