Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 40 additions & 10 deletions app/Actions/TagRepo.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use BookStack\Auth\Permissions\PermissionService;
use BookStack\Entities\Models\Entity;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

Expand All @@ -12,13 +13,42 @@ class TagRepo
protected $tag;
protected $permissionService;

public function __construct(PermissionService $ps)
{
$this->permissionService = $ps;
}

/**
* TagRepo constructor.
* Start a query against all tags in the system.
*/
public function __construct(Tag $tag, PermissionService $ps)
public function queryWithTotals(string $searchTerm, string $nameFilter): Builder
{
$this->tag = $tag;
$this->permissionService = $ps;
$groupingAttribute = $nameFilter ? 'value' : 'name';
$query = Tag::query()
->select([
'name',
($searchTerm || $nameFilter) ? 'value' : DB::raw('COUNT(distinct value) as `values`'),
DB::raw('COUNT(id) as usages'),
DB::raw('SUM(IF(entity_type = \'BookStack\\\\Page\', 1, 0)) as page_count'),
DB::raw('SUM(IF(entity_type = \'BookStack\\\\Chapter\', 1, 0)) as chapter_count'),
DB::raw('SUM(IF(entity_type = \'BookStack\\\\Book\', 1, 0)) as book_count'),
DB::raw('SUM(IF(entity_type = \'BookStack\\\\BookShelf\', 1, 0)) as shelf_count'),
])
->groupBy($groupingAttribute)
->orderBy($groupingAttribute);

if ($nameFilter) {
$query->where('name', '=', $nameFilter);
}

if ($searchTerm) {
$query->where(function(Builder $query) use ($searchTerm) {
$query->where('name', 'like', '%' . $searchTerm . '%')
->orWhere('value', 'like', '%' . $searchTerm . '%');
});
}

return $this->permissionService->filterRestrictedEntityRelations($query, 'tags', 'entity_id', 'entity_type');
}

/**
Expand All @@ -27,7 +57,7 @@ public function __construct(Tag $tag, PermissionService $ps)
*/
public function getNameSuggestions(?string $searchTerm): Collection
{
$query = $this->tag->newQuery()
$query = Tag::query()
->select('*', DB::raw('count(*) as count'))
->groupBy('name');

Expand All @@ -49,7 +79,7 @@ public function getNameSuggestions(?string $searchTerm): Collection
*/
public function getValueSuggestions(?string $searchTerm, ?string $tagName): Collection
{
$query = $this->tag->newQuery()
$query = Tag::query()
->select('*', DB::raw('count(*) as count'))
->groupBy('value');

Expand Down Expand Up @@ -90,9 +120,9 @@ public function saveTagsToEntity(Entity $entity, array $tags = []): iterable
*/
protected function newInstanceFromInput(array $input): Tag
{
$name = trim($input['name']);
$value = isset($input['value']) ? trim($input['value']) : '';

return $this->tag->newInstance(['name' => $name, 'value' => $value]);
return new Tag([
'name' => trim($input['name']),
'value' => trim($input['value'] ?? ''),
]);
}
}
22 changes: 22 additions & 0 deletions app/Http/Controllers/TagController.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,28 @@ public function __construct(TagRepo $tagRepo)
$this->tagRepo = $tagRepo;
}

/**
* Show a listing of existing tags in the system.
*/
public function index(Request $request)
{
$search = $request->get('search', '');
$nameFilter = $request->get('name', '');
$tags = $this->tagRepo
->queryWithTotals($search, $nameFilter)
->paginate(50)
->appends(array_filter([
'search' => $search,
'name' => $nameFilter
]));

return view('tags.index', [
'tags' => $tags,
'search' => $search,
'nameFilter' => $nameFilter,
]);
}

/**
* Get tag name suggestions from a given search term.
*/
Expand Down
1 change: 0 additions & 1 deletion resources/icons/info-filled.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions resources/icons/leaderboard.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions resources/lang/en/common.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
'unfavourite' => 'Unfavourite',
'next' => 'Next',
'previous' => 'Previous',
'filter_active' => 'Active Filter:',
'filter_clear' => 'Clear Filter',

// Sort Options
'sort_options' => 'Sort Options',
Expand Down
10 changes: 10 additions & 0 deletions resources/lang/en/entities.php
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,16 @@
'tags_explain' => "Add some tags to better categorise your content. \n You can assign a value to a tag for more in-depth organisation.",
'tags_add' => 'Add another tag',
'tags_remove' => 'Remove this tag',
'tags_usages' => 'Total tag usages',
'tags_assigned_pages' => 'Assigned to Pages',
'tags_assigned_chapters' => 'Assigned to Chapters',
'tags_assigned_books' => 'Assigned to Books',
'tags_assigned_shelves' => 'Assigned to Shelves',
'tags_x_unique_values' => ':count unique values',
'tags_all_values' => 'All values',
'tags_view_tags' => 'View Tags',
'tags_view_existing_tags' => 'View existing tags',
'tags_list_empty_hint' => 'Tags can be assigned via the page editor sidebar or while editing the details of a book, chapter or shelf.',
'attachments' => 'Attachments',
'attachments_explain' => 'Upload some files or attach some links to display on your page. These are visible in the page sidebar.',
'attachments_explain_instant_save' => 'Changes here are saved instantly.',
Expand Down
31 changes: 30 additions & 1 deletion resources/sass/_blocks.scss
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@
@include lightDark(border-color, #CCC, #666);
a, span, a:hover, a:active {
padding: 4px 8px;
@include lightDark(color, rgba(0, 0, 0, 0.6), rgba(255, 255, 255, 0.8));
@include lightDark(color, rgba(0, 0, 0, 0.7), rgba(255, 255, 255, 0.8));
transition: background-color ease-in-out 80ms;
text-decoration: none;
}
Expand All @@ -266,6 +266,35 @@
margin-bottom: 0;
}

td .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
*/
Expand Down
8 changes: 7 additions & 1 deletion resources/sass/_tables.scss
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ table.table {
font-weight: bold;
}
tr:hover {
@include lightDark(background-color, #eee, #333);
@include lightDark(background-color, #F2F2F2, #333);
}
.text-right {
text-align: end;
Expand All @@ -49,6 +49,12 @@ table.table {
a {
display: inline-block;
}
&.expand-to-padding {
margin-left: -$-s;
margin-right: -$-s;
width: calc(100% + (2*#{$-s}));
max-width: calc(100% + (2*#{$-s}));
}
}

table.no-style {
Expand Down
5 changes: 5 additions & 0 deletions resources/views/books/index.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
@endif

@include('entities.view-toggle', ['view' => $view, 'type' => 'books'])

<a href="{{ url('/tags') }}" class="icon-list-item">
<span>@icon('tag')</span>
<span>{{ trans('entities.tags_view_tags') }}</span>
</a>
</div>
</div>

Expand Down
10 changes: 1 addition & 9 deletions resources/views/entities/tag-list.blade.php
Original file line number Diff line number Diff line change
@@ -1,11 +1,3 @@
@foreach($entity->tags as $tag)
<div class="tag-item primary-background-light">
@if($linked ?? true)
<div class="tag-name"><a href="{{ $tag->nameUrl() }}">@icon('tag'){{ $tag->name }}</a></div>
@if($tag->value) <div class="tag-value"><a href="{{ $tag->valueUrl() }}">{{$tag->value}}</a></div> @endif
@else
<div class="tag-name"><span>@icon('tag'){{ $tag->name }}</span></div>
@if($tag->value) <div class="tag-value"><span>{{$tag->value}}</span></div> @endif
@endif
</div>
@include('entities.tag', ['tag' => $tag])
@endforeach
15 changes: 9 additions & 6 deletions resources/views/entities/tag-manager.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@
refs="tag-manager@add-remove"
class="tags">

<p class="text-muted small">{!! nl2br(e(trans('entities.tags_explain'))) !!}</p>
<p class="text-muted small">
{!! nl2br(e(trans('entities.tags_explain'))) !!} <br>
<a href="{{ url('/tags') }}" target="_blank">{{ trans('entities.tags_view_existing_tags') }}</a>.
</p>

<div component="sortable-list"
option:sortable-list:handle-selector=".handle">
@include('entities.tag-manager-list', ['tags' => $entity ? $entity->tags->all() : []])
</div>
<div component="sortable-list"
option:sortable-list:handle-selector=".handle">
@include('entities.tag-manager-list', ['tags' => $entity ? $entity->tags->all() : []])
</div>

<button refs="add-remove-rows@add" type="button" class="text-button">{{ trans('entities.tags_add') }}</button>
<button refs="add-remove-rows@add" type="button" class="text-button">{{ trans('entities.tags_add') }}</button>
</div>
9 changes: 9 additions & 0 deletions resources/views/entities/tag.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<div class="tag-item primary-background-light" data-name="{{ $tag->name }}" data-value="{{ $tag->value }}">
@if($linked ?? true)
<div class="tag-name"><a href="{{ $tag->nameUrl() }}">@icon('tag'){{ $tag->name }}</a></div>
@if($tag->value) <div class="tag-value"><a href="{{ $tag->valueUrl() }}">{{$tag->value}}</a></div> @endif
@else
<div class="tag-name"><span>@icon('tag'){{ $tag->name }}</span></div>
@if($tag->value) <div class="tag-value"><span>{{$tag->value}}</span></div> @endif
@endif
</div>
8 changes: 8 additions & 0 deletions resources/views/form/request-query-inputs.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{{--
$params - The query paramters to convert to inputs.
--}}
@foreach(array_intersect_key(request()->query(), array_flip($params)) as $name => $value)
@if ($value)
<input type="hidden" name="{{ $name }}" value="{{ $value }}">
@endif
@endforeach
6 changes: 6 additions & 0 deletions resources/views/shelves/index.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@
<span>{{ trans('entities.shelves_new_action') }}</span>
</a>
@endif

@include('entities.view-toggle', ['view' => $view, 'type' => 'shelves'])

<a href="{{ url('/tags') }}" class="icon-list-item">
<span>@icon('tag')</span>
<span>{{ trans('entities.tags_view_tags') }}</span>
</a>
</div>
</div>

Expand Down
56 changes: 56 additions & 0 deletions resources/views/tags/index.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
@extends('layouts.simple')

@section('body')
<div class="container small">

<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>

<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>
</div>
</div>

@if($nameFilter)
<div class="mb-m">
<span class="mr-xs">{{ trans('common.filter_active') }}</span>
@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']])
<button class="text-button text-warn">@icon('close'){{ trans('common.filter_clear') }}</button>
</form>
</div>
@endif

@if(count($tags) > 0)
<table class="table expand-to-padding mt-m">
@foreach($tags as $tag)
@include('tags.parts.table-row', ['tag' => $tag, 'nameFilter' => $nameFilter])
@endforeach
</table>

<div>
{{ $tags->links() }}
</div>
@else
<p class="text-muted italic my-xl">
{{ trans('common.no_items') }}.
<br>
{{ trans('entities.tags_list_empty_hint') }}
</p>
@endif
</main>

</div>

@stop
37 changes: 37 additions & 0 deletions resources/views/tags/parts/table-row.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<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>
Loading