Updated tags list to new responsive format

This commit is contained in:
Dan Brown 2022-10-31 11:40:28 +00:00
parent 9e8516c2df
commit 80d2889217
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
11 changed files with 109 additions and 90 deletions

View File

@ -4,6 +4,7 @@ namespace BookStack\Actions;
use BookStack\Auth\Permissions\PermissionApplicator;
use BookStack\Entities\Models\Entity;
use BookStack\Util\SimpleListOptions;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
@ -20,8 +21,14 @@ class TagRepo
/**
* Start a query against all tags in the system.
*/
public function queryWithTotals(string $searchTerm, string $nameFilter): Builder
public function queryWithTotals(SimpleListOptions $listOptions, string $nameFilter): Builder
{
$searchTerm = $listOptions->getSearch();
$sort = $listOptions->getSort();
if ($sort === 'name' && $nameFilter) {
$sort = 'value';
}
$query = Tag::query()
->select([
'name',
@ -32,7 +39,7 @@ class TagRepo
DB::raw('SUM(IF(entity_type = \'book\', 1, 0)) as book_count'),
DB::raw('SUM(IF(entity_type = \'bookshelf\', 1, 0)) as shelf_count'),
])
->orderBy($nameFilter ? 'value' : 'name');
->orderBy($sort, $listOptions->getOrder());
if ($nameFilter) {
$query->where('name', '=', $nameFilter);

View File

@ -3,6 +3,7 @@
namespace BookStack\Http\Controllers;
use BookStack\Actions\TagRepo;
use BookStack\Util\SimpleListOptions;
use Illuminate\Http\Request;
class TagController extends Controller
@ -19,22 +20,26 @@ class TagController extends Controller
*/
public function index(Request $request)
{
$search = $request->get('search', '');
$listOptions = SimpleListOptions::fromRequest($request, 'tags')->withSortOptions([
'name' => trans('common.sort_name'),
'usages' => trans('entities.tags_usages'),
]);
$nameFilter = $request->get('name', '');
$tags = $this->tagRepo
->queryWithTotals($search, $nameFilter)
->queryWithTotals($listOptions, $nameFilter)
->paginate(50)
->appends(array_filter([
'search' => $search,
...$listOptions->getPaginationAppends(),
'name' => $nameFilter,
]));
$this->setPageTitle(trans('entities.tags'));
return view('tags.index', [
'tags' => $tags,
'search' => $search,
'nameFilter' => $nameFilter,
'tags' => $tags,
'nameFilter' => $nameFilter,
'listOptions' => $listOptions,
]);
}

View File

@ -62,7 +62,7 @@ class UserPreferencesController extends Controller
*/
public function changeSort(Request $request, string $id, string $type)
{
$validSortTypes = ['books', 'bookshelves', 'shelf_books', 'users', 'roles', 'webhooks'];
$validSortTypes = ['books', 'bookshelves', 'shelf_books', 'users', 'roles', 'webhooks', 'tags'];
if (!in_array($type, $validSortTypes)) {
return redirect()->back(500);
}

View File

@ -275,6 +275,7 @@ return [
'shelf_tags' => 'Shelf Tags',
'tag' => 'Tag',
'tags' => 'Tags',
'tags_index_desc' => 'Tags can be applied to content within the system to apply a flexible form of categorization. Tags can have both a key and value, with the value being optional. Once applied, content can then be queried using the tag name and value.',
'tag_name' => 'Tag Name',
'tag_value' => 'Tag Value (Optional)',
'tags_explain' => "Add some tags to better categorise your content. \n You can assign a value to a tag for more in-depth organisation.",

View File

@ -286,35 +286,10 @@
margin-bottom: 0;
}
td .tag-item {
.item-list-row .tag-item {
margin-bottom: 0;
}
/**
* Pill boxes
*/
.pill {
display: inline-block;
border: 1px solid currentColor;
padding: .2em .8em;
font-size: 0.8em;
border-radius: 1rem;
position: relative;
overflow: hidden;
line-height: 1.4;
&:before {
content: '';
background-color: currentColor;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
opacity: 0.1;
}
}
/**
* API Docs
*/

View File

@ -160,6 +160,11 @@ body.flexbox {
flex-basis: auto;
flex-grow: 0;
}
&.fill-area {
flex-grow: 1;
flex-shrink: 0;
min-width: fit-content;
}
}
.flex-2 {

View File

@ -0,0 +1,28 @@
.opacity-10 {
opacity: 0.1;
}
.opacity-20 {
opacity: 0.2;
}
.opacity-30 {
opacity: 0.3;
}
.opacity-40 {
opacity: 0.4;
}
.opacity-50 {
opacity: 0.5;
}
.opacity-60 {
opacity: 0.6;
}
.opacity-70 {
opacity: 0.7;
}
.opacity-80 {
opacity: 0.8;
}
.opacity-90 {
opacity: 0.9;
}

View File

@ -4,6 +4,7 @@
@import "variables";
@import "mixins";
@import "spacing";
@import "opacity";
@import "html";
@import "text";
@import "colors";

View File

@ -5,25 +5,28 @@
<main class="card content-wrap mt-xxl">
<div class="flex-container-row wrap justify-space-between items-center mb-s">
<h1 class="list-heading">{{ trans('entities.tags') }}</h1>
<h1 class="list-heading">{{ trans('entities.tags') }}</h1>
<div>
<div class="block inline mr-xs">
<form method="get" action="{{ url("/tags") }}">
@include('form.request-query-inputs', ['params' => ['name']])
<input type="text"
name="search"
placeholder="{{ trans('common.search') }}"
value="{{ $search }}">
</form>
</div>
<p class="text-muted">{{ trans('entities.tags_index_desc') }}</p>
<div class="flex-container-row wrap justify-space-between items-center mb-s gap-m">
<div class="block inline mr-xs">
<form method="get" action="{{ url("/tags") }}">
@include('form.request-query-inputs', ['params' => ['name']])
<input type="text"
name="search"
placeholder="{{ trans('common.search') }}"
value="{{ $listOptions->getSearch() }}">
</form>
</div>
<div class="block inline">
@include('common.sort', $listOptions->getSortControlData())
</div>
</div>
@if($nameFilter)
<div class="mb-m">
<span class="mr-xs">{{ trans('common.filter_active') }}</span>
<div class="my-m">
<strong class="mr-xs">{{ trans('common.filter_active') }}</strong>
@include('entities.tag', ['tag' => new \BookStack\Actions\Tag(['name' => $nameFilter])])
<form method="get" action="{{ url("/tags") }}" class="inline block">
@include('form.request-query-inputs', ['params' => ['search']])
@ -33,13 +36,13 @@
@endif
@if(count($tags) > 0)
<table class="table expand-to-padding mt-m">
<div class="item-list mt-m">
@foreach($tags as $tag)
@include('tags.parts.table-row', ['tag' => $tag, 'nameFilter' => $nameFilter])
@include('tags.parts.tags-list-item', ['tag' => $tag, 'nameFilter' => $nameFilter])
@endforeach
</table>
</div>
<div>
<div class="my-m">
{{ $tags->links() }}
</div>
@else

View File

@ -1,37 +0,0 @@
<tr>
<td>
<span class="text-bigger mr-xl">@include('entities.tag', ['tag' => $tag])</span>
</td>
<td width="70" class="px-xs">
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() }}"
title="{{ trans('entities.tags_usages') }}"
class="pill text-muted">@icon('leaderboard'){{ $tag->usages }}</a>
</td>
<td width="70" class="px-xs">
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:page}' }}"
title="{{ trans('entities.tags_assigned_pages') }}"
class="pill text-page">@icon('page'){{ $tag->page_count }}</a>
</td>
<td width="70" class="px-xs">
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:chapter}' }}"
title="{{ trans('entities.tags_assigned_chapters') }}"
class="pill text-chapter">@icon('chapter'){{ $tag->chapter_count }}</a>
</td>
<td width="70" class="px-xs">
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:book}' }}"
title="{{ trans('entities.tags_assigned_books') }}"
class="pill text-book">@icon('book'){{ $tag->book_count }}</a>
</td>
<td width="70" class="px-xs">
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:bookshelf}' }}"
title="{{ trans('entities.tags_assigned_shelves') }}"
class="pill text-bookshelf">@icon('bookshelf'){{ $tag->shelf_count }}</a>
</td>
<td class="text-right text-muted">
@if($tag->values ?? false)
<a href="{{ url('/tags?name=' . urlencode($tag->name)) }}">{{ trans('entities.tags_x_unique_values', ['count' => $tag->values]) }}</a>
@elseif(empty($nameFilter))
<a href="{{ url('/tags?name=' . urlencode($tag->name)) }}">{{ trans('entities.tags_all_values') }}</a>
@endif
</td>
</tr>

View File

@ -0,0 +1,31 @@
<div class="item-list-row flex-container-row items-center wrap">
<div class="{{ isset($nameFilter) && $tag->value ? 'flex-2' : 'flex' }} py-s px-m min-width-m">
<span class="text-bigger mr-xl">@include('entities.tag', ['tag' => $tag])</span>
</div>
<div class="flex-2 flex-container-row justify-center items-center gap-m py-s px-m min-width-l wrap">
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() }}"
title="{{ trans('entities.tags_usages') }}"
class="flex fill-area min-width-xxs bold text-right text-muted"><span class="opacity-60">@icon('leaderboard')</span>{{ $tag->usages }}</a>
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:page}' }}"
title="{{ trans('entities.tags_assigned_pages') }}"
class="flex fill-area min-width-xxs bold text-right text-page"><span class="opacity-60">@icon('page')</span>{{ $tag->page_count }}</a>
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:chapter}' }}"
title="{{ trans('entities.tags_assigned_chapters') }}"
class="flex fill-area min-width-xxs bold text-right text-chapter"><span class="opacity-60">@icon('chapter')</span>{{ $tag->chapter_count }}</a>
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:book}' }}"
title="{{ trans('entities.tags_assigned_books') }}"
class="flex fill-area min-width-xxs bold text-right text-book"><span class="opacity-60">@icon('book')</span>{{ $tag->book_count }}</a>
<a href="{{ isset($tag->value) ? $tag->valueUrl() : $tag->nameUrl() . '+{type:bookshelf}' }}"
title="{{ trans('entities.tags_assigned_shelves') }}"
class="flex fill-area min-width-xxs bold text-right text-bookshelf"><span class="opacity-60">@icon('bookshelf')</span>{{ $tag->shelf_count }}</a>
</div>
@if($tag->values ?? false)
<div class="flex text-s-right text-muted py-s px-m min-width-s">
<a href="{{ url('/tags?name=' . urlencode($tag->name)) }}">{{ trans('entities.tags_x_unique_values', ['count' => $tag->values]) }}</a>
</div>
@elseif(empty($nameFilter))
<div class="flex text-s-right text-muted py-s px-m min-width-s">
<a href="{{ url('/tags?name=' . urlencode($tag->name)) }}">{{ trans('entities.tags_all_values') }}</a>
</div>
@endif
</div>