mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-11-22 13:31:49 +08:00
Merge pull request #3821 from BookStackApp/list_reworks
Revision of item list views
This commit is contained in:
commit
33e5c85503
4
.github/workflows/analyse-php.yml
vendored
4
.github/workflows/analyse-php.yml
vendored
|
@ -18,10 +18,10 @@ jobs:
|
||||||
- name: Get Composer Cache Directory
|
- name: Get Composer Cache Directory
|
||||||
id: composer-cache
|
id: composer-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Cache composer packages
|
- name: Cache composer packages
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.composer-cache.outputs.dir }}
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
key: ${{ runner.os }}-composer-8.1
|
key: ${{ runner.os }}-composer-8.1
|
||||||
|
|
4
.github/workflows/test-migrations.yml
vendored
4
.github/workflows/test-migrations.yml
vendored
|
@ -21,10 +21,10 @@ jobs:
|
||||||
- name: Get Composer Cache Directory
|
- name: Get Composer Cache Directory
|
||||||
id: composer-cache
|
id: composer-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Cache composer packages
|
- name: Cache composer packages
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.composer-cache.outputs.dir }}
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
key: ${{ runner.os }}-composer-${{ matrix.php }}
|
key: ${{ runner.os }}-composer-${{ matrix.php }}
|
||||||
|
|
4
.github/workflows/test-php.yml
vendored
4
.github/workflows/test-php.yml
vendored
|
@ -21,10 +21,10 @@ jobs:
|
||||||
- name: Get Composer Cache Directory
|
- name: Get Composer Cache Directory
|
||||||
id: composer-cache
|
id: composer-cache
|
||||||
run: |
|
run: |
|
||||||
echo "::set-output name=dir::$(composer config cache-files-dir)"
|
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Cache composer packages
|
- name: Cache composer packages
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v3
|
||||||
with:
|
with:
|
||||||
path: ${{ steps.composer-cache.outputs.dir }}
|
path: ${{ steps.composer-cache.outputs.dir }}
|
||||||
key: ${{ runner.os }}-composer-${{ matrix.php }}
|
key: ${{ runner.os }}-composer-${{ matrix.php }}
|
||||||
|
|
30
app/Actions/Queries/WebhooksAllPaginatedAndSorted.php
Normal file
30
app/Actions/Queries/WebhooksAllPaginatedAndSorted.php
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BookStack\Actions\Queries;
|
||||||
|
|
||||||
|
use BookStack\Actions\Webhook;
|
||||||
|
use BookStack\Util\SimpleListOptions;
|
||||||
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the webhooks in the system in a paginated format.
|
||||||
|
*/
|
||||||
|
class WebhooksAllPaginatedAndSorted
|
||||||
|
{
|
||||||
|
public function run(int $count, SimpleListOptions $listOptions): LengthAwarePaginator
|
||||||
|
{
|
||||||
|
$query = Webhook::query()->select(['*'])
|
||||||
|
->withCount(['trackedEvents'])
|
||||||
|
->orderBy($listOptions->getSort(), $listOptions->getOrder());
|
||||||
|
|
||||||
|
if ($listOptions->getSearch()) {
|
||||||
|
$term = '%' . $listOptions->getSearch() . '%';
|
||||||
|
$query->where(function ($query) use ($term) {
|
||||||
|
$query->where('name', 'like', $term)
|
||||||
|
->orWhere('endpoint', 'like', $term);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->paginate($count);
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,6 +4,7 @@ namespace BookStack\Actions;
|
||||||
|
|
||||||
use BookStack\Auth\Permissions\PermissionApplicator;
|
use BookStack\Auth\Permissions\PermissionApplicator;
|
||||||
use BookStack\Entities\Models\Entity;
|
use BookStack\Entities\Models\Entity;
|
||||||
|
use BookStack\Util\SimpleListOptions;
|
||||||
use Illuminate\Database\Eloquent\Builder;
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
@ -20,8 +21,14 @@ class TagRepo
|
||||||
/**
|
/**
|
||||||
* Start a query against all tags in the system.
|
* 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()
|
$query = Tag::query()
|
||||||
->select([
|
->select([
|
||||||
'name',
|
'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 = \'book\', 1, 0)) as book_count'),
|
||||||
DB::raw('SUM(IF(entity_type = \'bookshelf\', 1, 0)) as shelf_count'),
|
DB::raw('SUM(IF(entity_type = \'bookshelf\', 1, 0)) as shelf_count'),
|
||||||
])
|
])
|
||||||
->orderBy($nameFilter ? 'value' : 'name');
|
->orderBy($sort, $listOptions->getOrder());
|
||||||
|
|
||||||
if ($nameFilter) {
|
if ($nameFilter) {
|
||||||
$query->where('name', '=', $nameFilter);
|
$query->where('name', '=', $nameFilter);
|
||||||
|
|
35
app/Auth/Queries/RolesAllPaginatedAndSorted.php
Normal file
35
app/Auth/Queries/RolesAllPaginatedAndSorted.php
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BookStack\Auth\Queries;
|
||||||
|
|
||||||
|
use BookStack\Auth\Role;
|
||||||
|
use BookStack\Util\SimpleListOptions;
|
||||||
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the roles in the system in a paginated format.
|
||||||
|
*/
|
||||||
|
class RolesAllPaginatedAndSorted
|
||||||
|
{
|
||||||
|
public function run(int $count, SimpleListOptions $listOptions): LengthAwarePaginator
|
||||||
|
{
|
||||||
|
$sort = $listOptions->getSort();
|
||||||
|
if ($sort === 'created_at') {
|
||||||
|
$sort = 'users.created_at';
|
||||||
|
}
|
||||||
|
|
||||||
|
$query = Role::query()->select(['*'])
|
||||||
|
->withCount(['users', 'permissions'])
|
||||||
|
->orderBy($sort, $listOptions->getOrder());
|
||||||
|
|
||||||
|
if ($listOptions->getSearch()) {
|
||||||
|
$term = '%' . $listOptions->getSearch() . '%';
|
||||||
|
$query->where(function ($query) use ($term) {
|
||||||
|
$query->where('display_name', 'like', $term)
|
||||||
|
->orWhere('description', 'like', $term);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query->paginate($count);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@
|
||||||
namespace BookStack\Auth\Queries;
|
namespace BookStack\Auth\Queries;
|
||||||
|
|
||||||
use BookStack\Auth\User;
|
use BookStack\Auth\User;
|
||||||
|
use BookStack\Util\SimpleListOptions;
|
||||||
use Illuminate\Pagination\LengthAwarePaginator;
|
use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,23 +12,23 @@ use Illuminate\Pagination\LengthAwarePaginator;
|
||||||
* user is assumed to be trusted. (Admin users).
|
* user is assumed to be trusted. (Admin users).
|
||||||
* Email search can be abused to extract email addresses.
|
* Email search can be abused to extract email addresses.
|
||||||
*/
|
*/
|
||||||
class AllUsersPaginatedAndSorted
|
class UsersAllPaginatedAndSorted
|
||||||
{
|
{
|
||||||
/**
|
public function run(int $count, SimpleListOptions $listOptions): LengthAwarePaginator
|
||||||
* @param array{sort: string, order: string, search: string} $sortData
|
|
||||||
*/
|
|
||||||
public function run(int $count, array $sortData): LengthAwarePaginator
|
|
||||||
{
|
{
|
||||||
$sort = $sortData['sort'];
|
$sort = $listOptions->getSort();
|
||||||
|
if ($sort === 'created_at') {
|
||||||
|
$sort = 'users.created_at';
|
||||||
|
}
|
||||||
|
|
||||||
$query = User::query()->select(['*'])
|
$query = User::query()->select(['*'])
|
||||||
->scopes(['withLastActivityAt'])
|
->scopes(['withLastActivityAt'])
|
||||||
->with(['roles', 'avatar'])
|
->with(['roles', 'avatar'])
|
||||||
->withCount('mfaValues')
|
->withCount('mfaValues')
|
||||||
->orderBy($sort, $sortData['order']);
|
->orderBy($sort, $listOptions->getOrder());
|
||||||
|
|
||||||
if ($sortData['search']) {
|
if ($listOptions->getSearch()) {
|
||||||
$term = '%' . $sortData['search'] . '%';
|
$term = '%' . $listOptions->getSearch() . '%';
|
||||||
$query->where(function ($query) use ($term) {
|
$query->where(function ($query) use ($term) {
|
||||||
$query->where('name', 'like', $term)
|
$query->where('name', 'like', $term)
|
||||||
->orWhere('email', 'like', $term);
|
->orWhere('email', 'like', $term);
|
|
@ -110,14 +110,6 @@ class Role extends Model implements Loggable
|
||||||
return static::query()->where('system_name', '=', $systemName)->first();
|
return static::query()->where('system_name', '=', $systemName)->first();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all visible roles.
|
|
||||||
*/
|
|
||||||
public static function visible(): Collection
|
|
||||||
{
|
|
||||||
return static::query()->where('hidden', '=', false)->orderBy('name')->get();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
namespace BookStack\Http\Controllers;
|
namespace BookStack\Http\Controllers;
|
||||||
|
|
||||||
use BookStack\Actions\Activity;
|
use BookStack\Actions\Activity;
|
||||||
|
use BookStack\Actions\ActivityType;
|
||||||
|
use BookStack\Util\SimpleListOptions;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
|
||||||
|
@ -13,10 +15,15 @@ class AuditLogController extends Controller
|
||||||
$this->checkPermission('settings-manage');
|
$this->checkPermission('settings-manage');
|
||||||
$this->checkPermission('users-manage');
|
$this->checkPermission('users-manage');
|
||||||
|
|
||||||
$listDetails = [
|
$sort = $request->get('sort', 'activity_date');
|
||||||
'order' => $request->get('order', 'desc'),
|
$order = $request->get('order', 'desc');
|
||||||
|
$listOptions = (new SimpleListOptions('', $sort, $order))->withSortOptions([
|
||||||
|
'created_at' => trans('settings.audit_table_date'),
|
||||||
|
'type' => trans('settings.audit_table_event'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$filters = [
|
||||||
'event' => $request->get('event', ''),
|
'event' => $request->get('event', ''),
|
||||||
'sort' => $request->get('sort', 'created_at'),
|
|
||||||
'date_from' => $request->get('date_from', ''),
|
'date_from' => $request->get('date_from', ''),
|
||||||
'date_to' => $request->get('date_to', ''),
|
'date_to' => $request->get('date_to', ''),
|
||||||
'user' => $request->get('user', ''),
|
'user' => $request->get('user', ''),
|
||||||
|
@ -25,39 +32,38 @@ class AuditLogController extends Controller
|
||||||
|
|
||||||
$query = Activity::query()
|
$query = Activity::query()
|
||||||
->with([
|
->with([
|
||||||
'entity' => function ($query) {
|
'entity' => fn ($query) => $query->withTrashed(),
|
||||||
$query->withTrashed();
|
|
||||||
},
|
|
||||||
'user',
|
'user',
|
||||||
])
|
])
|
||||||
->orderBy($listDetails['sort'], $listDetails['order']);
|
->orderBy($listOptions->getSort(), $listOptions->getOrder());
|
||||||
|
|
||||||
if ($listDetails['event']) {
|
if ($filters['event']) {
|
||||||
$query->where('type', '=', $listDetails['event']);
|
$query->where('type', '=', $filters['event']);
|
||||||
}
|
}
|
||||||
if ($listDetails['user']) {
|
if ($filters['user']) {
|
||||||
$query->where('user_id', '=', $listDetails['user']);
|
$query->where('user_id', '=', $filters['user']);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($listDetails['date_from']) {
|
if ($filters['date_from']) {
|
||||||
$query->where('created_at', '>=', $listDetails['date_from']);
|
$query->where('created_at', '>=', $filters['date_from']);
|
||||||
}
|
}
|
||||||
if ($listDetails['date_to']) {
|
if ($filters['date_to']) {
|
||||||
$query->where('created_at', '<=', $listDetails['date_to']);
|
$query->where('created_at', '<=', $filters['date_to']);
|
||||||
}
|
}
|
||||||
if ($listDetails['ip']) {
|
if ($filters['ip']) {
|
||||||
$query->where('ip', 'like', $listDetails['ip'] . '%');
|
$query->where('ip', 'like', $filters['ip'] . '%');
|
||||||
}
|
}
|
||||||
|
|
||||||
$activities = $query->paginate(100);
|
$activities = $query->paginate(100);
|
||||||
$activities->appends($listDetails);
|
$activities->appends($request->all());
|
||||||
|
|
||||||
$types = DB::table('activities')->select('type')->distinct()->pluck('type');
|
$types = ActivityType::all();
|
||||||
$this->setPageTitle(trans('settings.audit'));
|
$this->setPageTitle(trans('settings.audit'));
|
||||||
|
|
||||||
return view('settings.audit', [
|
return view('settings.audit', [
|
||||||
'activities' => $activities,
|
'activities' => $activities,
|
||||||
'listDetails' => $listDetails,
|
'filters' => $filters,
|
||||||
|
'listOptions' => $listOptions,
|
||||||
'activityTypes' => $types,
|
'activityTypes' => $types,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ use BookStack\Exceptions\ImageUploadException;
|
||||||
use BookStack\Exceptions\NotFoundException;
|
use BookStack\Exceptions\NotFoundException;
|
||||||
use BookStack\Facades\Activity;
|
use BookStack\Facades\Activity;
|
||||||
use BookStack\References\ReferenceFetcher;
|
use BookStack\References\ReferenceFetcher;
|
||||||
|
use BookStack\Util\SimpleListOptions;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
|
@ -35,13 +36,16 @@ class BookController extends Controller
|
||||||
/**
|
/**
|
||||||
* Display a listing of the book.
|
* Display a listing of the book.
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$view = setting()->getForCurrentUser('books_view_type');
|
$view = setting()->getForCurrentUser('books_view_type');
|
||||||
$sort = setting()->getForCurrentUser('books_sort', 'name');
|
$listOptions = SimpleListOptions::fromRequest($request, 'books')->withSortOptions([
|
||||||
$order = setting()->getForCurrentUser('books_sort_order', 'asc');
|
'name' => trans('common.sort_name'),
|
||||||
|
'created_at' => trans('common.sort_created_at'),
|
||||||
|
'updated_at' => trans('common.sort_updated_at'),
|
||||||
|
]);
|
||||||
|
|
||||||
$books = $this->bookRepo->getAllPaginated(18, $sort, $order);
|
$books = $this->bookRepo->getAllPaginated(18, $listOptions->getSort(), $listOptions->getOrder());
|
||||||
$recents = $this->isSignedIn() ? $this->bookRepo->getRecentlyViewed(4) : false;
|
$recents = $this->isSignedIn() ? $this->bookRepo->getRecentlyViewed(4) : false;
|
||||||
$popular = $this->bookRepo->getPopular(4);
|
$popular = $this->bookRepo->getPopular(4);
|
||||||
$new = $this->bookRepo->getRecentlyCreated(4);
|
$new = $this->bookRepo->getRecentlyCreated(4);
|
||||||
|
@ -56,8 +60,7 @@ class BookController extends Controller
|
||||||
'popular' => $popular,
|
'popular' => $popular,
|
||||||
'new' => $new,
|
'new' => $new,
|
||||||
'view' => $view,
|
'view' => $view,
|
||||||
'sort' => $sort,
|
'listOptions' => $listOptions,
|
||||||
'order' => $order,
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@ use BookStack\Entities\Tools\ShelfContext;
|
||||||
use BookStack\Exceptions\ImageUploadException;
|
use BookStack\Exceptions\ImageUploadException;
|
||||||
use BookStack\Exceptions\NotFoundException;
|
use BookStack\Exceptions\NotFoundException;
|
||||||
use BookStack\References\ReferenceFetcher;
|
use BookStack\References\ReferenceFetcher;
|
||||||
|
use BookStack\Util\SimpleListOptions;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
|
@ -30,18 +31,16 @@ class BookshelfController extends Controller
|
||||||
/**
|
/**
|
||||||
* Display a listing of the book.
|
* Display a listing of the book.
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$view = setting()->getForCurrentUser('bookshelves_view_type');
|
$view = setting()->getForCurrentUser('bookshelves_view_type');
|
||||||
$sort = setting()->getForCurrentUser('bookshelves_sort', 'name');
|
$listOptions = SimpleListOptions::fromRequest($request, 'bookshelves')->withSortOptions([
|
||||||
$order = setting()->getForCurrentUser('bookshelves_sort_order', 'asc');
|
|
||||||
$sortOptions = [
|
|
||||||
'name' => trans('common.sort_name'),
|
'name' => trans('common.sort_name'),
|
||||||
'created_at' => trans('common.sort_created_at'),
|
'created_at' => trans('common.sort_created_at'),
|
||||||
'updated_at' => trans('common.sort_updated_at'),
|
'updated_at' => trans('common.sort_updated_at'),
|
||||||
];
|
]);
|
||||||
|
|
||||||
$shelves = $this->shelfRepo->getAllPaginated(18, $sort, $order);
|
$shelves = $this->shelfRepo->getAllPaginated(18, $listOptions->getSort(), $listOptions->getOrder());
|
||||||
$recents = $this->isSignedIn() ? $this->shelfRepo->getRecentlyViewed(4) : false;
|
$recents = $this->isSignedIn() ? $this->shelfRepo->getRecentlyViewed(4) : false;
|
||||||
$popular = $this->shelfRepo->getPopular(4);
|
$popular = $this->shelfRepo->getPopular(4);
|
||||||
$new = $this->shelfRepo->getRecentlyCreated(4);
|
$new = $this->shelfRepo->getRecentlyCreated(4);
|
||||||
|
@ -55,9 +54,7 @@ class BookshelfController extends Controller
|
||||||
'popular' => $popular,
|
'popular' => $popular,
|
||||||
'new' => $new,
|
'new' => $new,
|
||||||
'view' => $view,
|
'view' => $view,
|
||||||
'sort' => $sort,
|
'listOptions' => $listOptions,
|
||||||
'order' => $order,
|
|
||||||
'sortOptions' => $sortOptions,
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,16 +97,21 @@ class BookshelfController extends Controller
|
||||||
*
|
*
|
||||||
* @throws NotFoundException
|
* @throws NotFoundException
|
||||||
*/
|
*/
|
||||||
public function show(ActivityQueries $activities, string $slug)
|
public function show(Request $request, ActivityQueries $activities, string $slug)
|
||||||
{
|
{
|
||||||
$shelf = $this->shelfRepo->getBySlug($slug);
|
$shelf = $this->shelfRepo->getBySlug($slug);
|
||||||
$this->checkOwnablePermission('bookshelf-view', $shelf);
|
$this->checkOwnablePermission('bookshelf-view', $shelf);
|
||||||
|
|
||||||
$sort = setting()->getForCurrentUser('shelf_books_sort', 'default');
|
$listOptions = SimpleListOptions::fromRequest($request, 'shelf_books')->withSortOptions([
|
||||||
$order = setting()->getForCurrentUser('shelf_books_sort_order', 'asc');
|
'default' => trans('common.sort_default'),
|
||||||
|
'name' => trans('common.sort_name'),
|
||||||
|
'created_at' => trans('common.sort_created_at'),
|
||||||
|
'updated_at' => trans('common.sort_updated_at'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$sort = $listOptions->getSort();
|
||||||
$sortedVisibleShelfBooks = $shelf->visibleBooks()->get()
|
$sortedVisibleShelfBooks = $shelf->visibleBooks()->get()
|
||||||
->sortBy($sort === 'default' ? 'pivot.order' : $sort, SORT_REGULAR, $order === 'desc')
|
->sortBy($sort === 'default' ? 'pivot.order' : $sort, SORT_REGULAR, $listOptions->getOrder() === 'desc')
|
||||||
->values()
|
->values()
|
||||||
->all();
|
->all();
|
||||||
|
|
||||||
|
@ -124,8 +126,7 @@ class BookshelfController extends Controller
|
||||||
'sortedVisibleShelfBooks' => $sortedVisibleShelfBooks,
|
'sortedVisibleShelfBooks' => $sortedVisibleShelfBooks,
|
||||||
'view' => $view,
|
'view' => $view,
|
||||||
'activity' => $activities->entityActivity($shelf, 20, 1),
|
'activity' => $activities->entityActivity($shelf, 20, 1),
|
||||||
'order' => $order,
|
'listOptions' => $listOptions,
|
||||||
'sort' => $sort,
|
|
||||||
'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($shelf),
|
'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($shelf),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,13 +10,15 @@ use BookStack\Entities\Queries\TopFavourites;
|
||||||
use BookStack\Entities\Repos\BookRepo;
|
use BookStack\Entities\Repos\BookRepo;
|
||||||
use BookStack\Entities\Repos\BookshelfRepo;
|
use BookStack\Entities\Repos\BookshelfRepo;
|
||||||
use BookStack\Entities\Tools\PageContent;
|
use BookStack\Entities\Tools\PageContent;
|
||||||
|
use BookStack\Util\SimpleListOptions;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class HomeController extends Controller
|
class HomeController extends Controller
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Display the homepage.
|
* Display the homepage.
|
||||||
*/
|
*/
|
||||||
public function index(ActivityQueries $activities)
|
public function index(Request $request, ActivityQueries $activities)
|
||||||
{
|
{
|
||||||
$activity = $activities->latest(10);
|
$activity = $activities->latest(10);
|
||||||
$draftPages = [];
|
$draftPages = [];
|
||||||
|
@ -61,33 +63,27 @@ class HomeController extends Controller
|
||||||
if ($homepageOption === 'bookshelves' || $homepageOption === 'books') {
|
if ($homepageOption === 'bookshelves' || $homepageOption === 'books') {
|
||||||
$key = $homepageOption;
|
$key = $homepageOption;
|
||||||
$view = setting()->getForCurrentUser($key . '_view_type');
|
$view = setting()->getForCurrentUser($key . '_view_type');
|
||||||
$sort = setting()->getForCurrentUser($key . '_sort', 'name');
|
$listOptions = SimpleListOptions::fromRequest($request, $key)->withSortOptions([
|
||||||
$order = setting()->getForCurrentUser($key . '_sort_order', 'asc');
|
'name' => trans('common.sort_name'),
|
||||||
|
|
||||||
$sortOptions = [
|
|
||||||
'name' => trans('common.sort_name'),
|
|
||||||
'created_at' => trans('common.sort_created_at'),
|
'created_at' => trans('common.sort_created_at'),
|
||||||
'updated_at' => trans('common.sort_updated_at'),
|
'updated_at' => trans('common.sort_updated_at'),
|
||||||
];
|
]);
|
||||||
|
|
||||||
$commonData = array_merge($commonData, [
|
$commonData = array_merge($commonData, [
|
||||||
'view' => $view,
|
'view' => $view,
|
||||||
'sort' => $sort,
|
'listOptions' => $listOptions,
|
||||||
'order' => $order,
|
|
||||||
'sortOptions' => $sortOptions,
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($homepageOption === 'bookshelves') {
|
if ($homepageOption === 'bookshelves') {
|
||||||
$shelves = app(BookshelfRepo::class)->getAllPaginated(18, $commonData['sort'], $commonData['order']);
|
$shelves = app(BookshelfRepo::class)->getAllPaginated(18, $commonData['listOptions']->getSort(), $commonData['listOptions']->getOrder());
|
||||||
$data = array_merge($commonData, ['shelves' => $shelves]);
|
$data = array_merge($commonData, ['shelves' => $shelves]);
|
||||||
|
|
||||||
return view('home.shelves', $data);
|
return view('home.shelves', $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($homepageOption === 'books') {
|
if ($homepageOption === 'books') {
|
||||||
$bookRepo = app(BookRepo::class);
|
$books = app(BookRepo::class)->getAllPaginated(18, $commonData['listOptions']->getSort(), $commonData['listOptions']->getOrder());
|
||||||
$books = $bookRepo->getAllPaginated(18, $commonData['sort'], $commonData['order']);
|
|
||||||
$data = array_merge($commonData, ['books' => $books]);
|
$data = array_merge($commonData, ['books' => $books]);
|
||||||
|
|
||||||
return view('home.books', $data);
|
return view('home.books', $data);
|
||||||
|
|
|
@ -8,6 +8,8 @@ use BookStack\Entities\Repos\PageRepo;
|
||||||
use BookStack\Entities\Tools\PageContent;
|
use BookStack\Entities\Tools\PageContent;
|
||||||
use BookStack\Exceptions\NotFoundException;
|
use BookStack\Exceptions\NotFoundException;
|
||||||
use BookStack\Facades\Activity;
|
use BookStack\Facades\Activity;
|
||||||
|
use BookStack\Util\SimpleListOptions;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Ssddanbrown\HtmlDiff\Diff;
|
use Ssddanbrown\HtmlDiff\Diff;
|
||||||
|
|
||||||
class PageRevisionController extends Controller
|
class PageRevisionController extends Controller
|
||||||
|
@ -24,22 +26,29 @@ class PageRevisionController extends Controller
|
||||||
*
|
*
|
||||||
* @throws NotFoundException
|
* @throws NotFoundException
|
||||||
*/
|
*/
|
||||||
public function index(string $bookSlug, string $pageSlug)
|
public function index(Request $request, string $bookSlug, string $pageSlug)
|
||||||
{
|
{
|
||||||
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
$page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
|
||||||
|
$listOptions = SimpleListOptions::fromRequest($request, 'page_revisions', true)->withSortOptions([
|
||||||
|
'id' => trans('entities.pages_revisions_sort_number')
|
||||||
|
]);
|
||||||
|
|
||||||
$revisions = $page->revisions()->select([
|
$revisions = $page->revisions()->select([
|
||||||
'id', 'page_id', 'name', 'created_at', 'created_by', 'updated_at',
|
'id', 'page_id', 'name', 'created_at', 'created_by', 'updated_at',
|
||||||
'type', 'revision_number', 'summary',
|
'type', 'revision_number', 'summary',
|
||||||
])
|
])
|
||||||
->selectRaw("IF(markdown = '', false, true) as is_markdown")
|
->selectRaw("IF(markdown = '', false, true) as is_markdown")
|
||||||
->with(['page.book', 'createdBy'])
|
->with(['page.book', 'createdBy'])
|
||||||
->get();
|
->reorder('id', $listOptions->getOrder())
|
||||||
|
->reorder('created_at', $listOptions->getOrder())
|
||||||
|
->paginate(50);
|
||||||
|
|
||||||
$this->setPageTitle(trans('entities.pages_revisions_named', ['pageName' => $page->getShortName()]));
|
$this->setPageTitle(trans('entities.pages_revisions_named', ['pageName' => $page->getShortName()]));
|
||||||
|
|
||||||
return view('pages.revisions', [
|
return view('pages.revisions', [
|
||||||
'revisions' => $revisions,
|
'revisions' => $revisions,
|
||||||
'page' => $page,
|
'page' => $page,
|
||||||
|
'listOptions' => $listOptions,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,19 +3,18 @@
|
||||||
namespace BookStack\Http\Controllers;
|
namespace BookStack\Http\Controllers;
|
||||||
|
|
||||||
use BookStack\Auth\Permissions\PermissionsRepo;
|
use BookStack\Auth\Permissions\PermissionsRepo;
|
||||||
|
use BookStack\Auth\Queries\RolesAllPaginatedAndSorted;
|
||||||
use BookStack\Auth\Role;
|
use BookStack\Auth\Role;
|
||||||
use BookStack\Exceptions\PermissionsException;
|
use BookStack\Exceptions\PermissionsException;
|
||||||
|
use BookStack\Util\SimpleListOptions;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Validation\ValidationException;
|
use Illuminate\Validation\ValidationException;
|
||||||
|
|
||||||
class RoleController extends Controller
|
class RoleController extends Controller
|
||||||
{
|
{
|
||||||
protected $permissionsRepo;
|
protected PermissionsRepo $permissionsRepo;
|
||||||
|
|
||||||
/**
|
|
||||||
* PermissionController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(PermissionsRepo $permissionsRepo)
|
public function __construct(PermissionsRepo $permissionsRepo)
|
||||||
{
|
{
|
||||||
$this->permissionsRepo = $permissionsRepo;
|
$this->permissionsRepo = $permissionsRepo;
|
||||||
|
@ -24,14 +23,27 @@ class RoleController extends Controller
|
||||||
/**
|
/**
|
||||||
* Show a listing of the roles in the system.
|
* Show a listing of the roles in the system.
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$this->checkPermission('user-roles-manage');
|
$this->checkPermission('user-roles-manage');
|
||||||
$roles = $this->permissionsRepo->getAllRoles();
|
|
||||||
|
$listOptions = SimpleListOptions::fromRequest($request, 'roles')->withSortOptions([
|
||||||
|
'display_name' => trans('common.sort_name'),
|
||||||
|
'users_count' => trans('settings.roles_assigned_users'),
|
||||||
|
'permissions_count' => trans('settings.roles_permissions_provided'),
|
||||||
|
'created_at' => trans('common.sort_created_at'),
|
||||||
|
'updated_at' => trans('common.sort_updated_at'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$roles = (new RolesAllPaginatedAndSorted())->run(20, $listOptions);
|
||||||
|
$roles->appends($listOptions->getPaginationAppends());
|
||||||
|
|
||||||
$this->setPageTitle(trans('settings.roles'));
|
$this->setPageTitle(trans('settings.roles'));
|
||||||
|
|
||||||
return view('settings.roles.index', ['roles' => $roles]);
|
return view('settings.roles.index', [
|
||||||
|
'roles' => $roles,
|
||||||
|
'listOptions' => $listOptions,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -75,16 +87,11 @@ class RoleController extends Controller
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Show the form for editing a user role.
|
* Show the form for editing a user role.
|
||||||
*
|
|
||||||
* @throws PermissionsException
|
|
||||||
*/
|
*/
|
||||||
public function edit(string $id)
|
public function edit(string $id)
|
||||||
{
|
{
|
||||||
$this->checkPermission('user-roles-manage');
|
$this->checkPermission('user-roles-manage');
|
||||||
$role = $this->permissionsRepo->getRoleById($id);
|
$role = $this->permissionsRepo->getRoleById($id);
|
||||||
if ($role->hidden) {
|
|
||||||
throw new PermissionsException(trans('errors.role_cannot_be_edited'));
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->setPageTitle(trans('settings.role_edit'));
|
$this->setPageTitle(trans('settings.role_edit'));
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace BookStack\Http\Controllers;
|
namespace BookStack\Http\Controllers;
|
||||||
|
|
||||||
use BookStack\Actions\TagRepo;
|
use BookStack\Actions\TagRepo;
|
||||||
|
use BookStack\Util\SimpleListOptions;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class TagController extends Controller
|
class TagController extends Controller
|
||||||
|
@ -19,22 +20,25 @@ class TagController extends Controller
|
||||||
*/
|
*/
|
||||||
public function index(Request $request)
|
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', '');
|
$nameFilter = $request->get('name', '');
|
||||||
$tags = $this->tagRepo
|
$tags = $this->tagRepo
|
||||||
->queryWithTotals($search, $nameFilter)
|
->queryWithTotals($listOptions, $nameFilter)
|
||||||
->paginate(50)
|
->paginate(50)
|
||||||
->appends(array_filter([
|
->appends(array_filter(array_merge($listOptions->getPaginationAppends(), [
|
||||||
'search' => $search,
|
|
||||||
'name' => $nameFilter,
|
'name' => $nameFilter,
|
||||||
]));
|
])));
|
||||||
|
|
||||||
$this->setPageTitle(trans('entities.tags'));
|
$this->setPageTitle(trans('entities.tags'));
|
||||||
|
|
||||||
return view('tags.index', [
|
return view('tags.index', [
|
||||||
'tags' => $tags,
|
'tags' => $tags,
|
||||||
'search' => $search,
|
'nameFilter' => $nameFilter,
|
||||||
'nameFilter' => $nameFilter,
|
'listOptions' => $listOptions,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
namespace BookStack\Http\Controllers;
|
namespace BookStack\Http\Controllers;
|
||||||
|
|
||||||
use BookStack\Auth\Access\SocialAuthService;
|
use BookStack\Auth\Access\SocialAuthService;
|
||||||
use BookStack\Auth\Queries\AllUsersPaginatedAndSorted;
|
use BookStack\Auth\Queries\UsersAllPaginatedAndSorted;
|
||||||
use BookStack\Auth\Role;
|
use BookStack\Auth\Role;
|
||||||
use BookStack\Auth\User;
|
|
||||||
use BookStack\Auth\UserRepo;
|
use BookStack\Auth\UserRepo;
|
||||||
use BookStack\Exceptions\ImageUploadException;
|
use BookStack\Exceptions\ImageUploadException;
|
||||||
use BookStack\Exceptions\UserUpdateException;
|
use BookStack\Exceptions\UserUpdateException;
|
||||||
use BookStack\Uploads\ImageRepo;
|
use BookStack\Uploads\ImageRepo;
|
||||||
|
use BookStack\Util\SimpleListOptions;
|
||||||
use Exception;
|
use Exception;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
|
@ -21,9 +21,6 @@ class UserController extends Controller
|
||||||
protected UserRepo $userRepo;
|
protected UserRepo $userRepo;
|
||||||
protected ImageRepo $imageRepo;
|
protected ImageRepo $imageRepo;
|
||||||
|
|
||||||
/**
|
|
||||||
* UserController constructor.
|
|
||||||
*/
|
|
||||||
public function __construct(UserRepo $userRepo, ImageRepo $imageRepo)
|
public function __construct(UserRepo $userRepo, ImageRepo $imageRepo)
|
||||||
{
|
{
|
||||||
$this->userRepo = $userRepo;
|
$this->userRepo = $userRepo;
|
||||||
|
@ -36,20 +33,23 @@ class UserController extends Controller
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$this->checkPermission('users-manage');
|
$this->checkPermission('users-manage');
|
||||||
$listDetails = [
|
|
||||||
'order' => $request->get('order', 'asc'),
|
|
||||||
'search' => $request->get('search', ''),
|
|
||||||
'sort' => $request->get('sort', 'name'),
|
|
||||||
];
|
|
||||||
|
|
||||||
$users = (new AllUsersPaginatedAndSorted())->run(20, $listDetails);
|
$listOptions = SimpleListOptions::fromRequest($request, 'users')->withSortOptions([
|
||||||
|
'name' => trans('common.sort_name'),
|
||||||
|
'email' => trans('auth.email'),
|
||||||
|
'created_at' => trans('common.sort_created_at'),
|
||||||
|
'updated_at' => trans('common.sort_updated_at'),
|
||||||
|
'last_activity_at' => trans('settings.users_latest_activity'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$users = (new UsersAllPaginatedAndSorted())->run(20, $listOptions);
|
||||||
|
|
||||||
$this->setPageTitle(trans('settings.users'));
|
$this->setPageTitle(trans('settings.users'));
|
||||||
$users->appends($listDetails);
|
$users->appends($listOptions->getPaginationAppends());
|
||||||
|
|
||||||
return view('users.index', [
|
return view('users.index', [
|
||||||
'users' => $users,
|
'users' => $users,
|
||||||
'listDetails' => $listDetails,
|
'listOptions' => $listOptions,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -107,9 +107,8 @@ class UserController extends Controller
|
||||||
{
|
{
|
||||||
$this->checkPermissionOrCurrentUser('users-manage', $id);
|
$this->checkPermissionOrCurrentUser('users-manage', $id);
|
||||||
|
|
||||||
/** @var User $user */
|
$user = $this->userRepo->getById($id);
|
||||||
$user = User::query()->with(['apiTokens', 'mfaValues'])->findOrFail($id);
|
$user->load(['apiTokens', 'mfaValues']);
|
||||||
|
|
||||||
$authMethod = ($user->system_name) ? 'system' : config('auth.method');
|
$authMethod = ($user->system_name) ? 'system' : config('auth.method');
|
||||||
|
|
||||||
$activeSocialDrivers = $socialAuthService->getActiveDrivers();
|
$activeSocialDrivers = $socialAuthService->getActiveDrivers();
|
||||||
|
@ -202,137 +201,4 @@ class UserController extends Controller
|
||||||
|
|
||||||
return redirect('/settings/users');
|
return redirect('/settings/users');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the user's preferred book-list display setting.
|
|
||||||
*/
|
|
||||||
public function switchBooksView(Request $request, int $id)
|
|
||||||
{
|
|
||||||
return $this->switchViewType($id, $request, 'books');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the user's preferred shelf-list display setting.
|
|
||||||
*/
|
|
||||||
public function switchShelvesView(Request $request, int $id)
|
|
||||||
{
|
|
||||||
return $this->switchViewType($id, $request, 'bookshelves');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the user's preferred shelf-view book list display setting.
|
|
||||||
*/
|
|
||||||
public function switchShelfView(Request $request, int $id)
|
|
||||||
{
|
|
||||||
return $this->switchViewType($id, $request, 'bookshelf');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For a type of list, switch with stored view type for a user.
|
|
||||||
*/
|
|
||||||
protected function switchViewType(int $userId, Request $request, string $listName)
|
|
||||||
{
|
|
||||||
$this->checkPermissionOrCurrentUser('users-manage', $userId);
|
|
||||||
|
|
||||||
$viewType = $request->get('view_type');
|
|
||||||
if (!in_array($viewType, ['grid', 'list'])) {
|
|
||||||
$viewType = 'list';
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = $this->userRepo->getById($userId);
|
|
||||||
$key = $listName . '_view_type';
|
|
||||||
setting()->putUser($user, $key, $viewType);
|
|
||||||
|
|
||||||
return redirect()->back(302, [], "/settings/users/$userId");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change the stored sort type for a particular view.
|
|
||||||
*/
|
|
||||||
public function changeSort(Request $request, string $id, string $type)
|
|
||||||
{
|
|
||||||
$validSortTypes = ['books', 'bookshelves', 'shelf_books'];
|
|
||||||
if (!in_array($type, $validSortTypes)) {
|
|
||||||
return redirect()->back(500);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->changeListSort($id, $request, $type);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggle dark mode for the current user.
|
|
||||||
*/
|
|
||||||
public function toggleDarkMode()
|
|
||||||
{
|
|
||||||
$enabled = setting()->getForCurrentUser('dark-mode-enabled', false);
|
|
||||||
setting()->putUser(user(), 'dark-mode-enabled', $enabled ? 'false' : 'true');
|
|
||||||
|
|
||||||
return redirect()->back();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update the stored section expansion preference for the given user.
|
|
||||||
*/
|
|
||||||
public function updateExpansionPreference(Request $request, string $id, string $key)
|
|
||||||
{
|
|
||||||
$this->checkPermissionOrCurrentUser('users-manage', $id);
|
|
||||||
$keyWhitelist = ['home-details'];
|
|
||||||
if (!in_array($key, $keyWhitelist)) {
|
|
||||||
return response('Invalid key', 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
$newState = $request->get('expand', 'false');
|
|
||||||
|
|
||||||
$user = $this->userRepo->getById($id);
|
|
||||||
setting()->putUser($user, 'section_expansion#' . $key, $newState);
|
|
||||||
|
|
||||||
return response('', 204);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function updateCodeLanguageFavourite(Request $request)
|
|
||||||
{
|
|
||||||
$validated = $this->validate($request, [
|
|
||||||
'language' => ['required', 'string', 'max:20'],
|
|
||||||
'active' => ['required', 'bool'],
|
|
||||||
]);
|
|
||||||
|
|
||||||
$currentFavoritesStr = setting()->getForCurrentUser('code-language-favourites', '');
|
|
||||||
$currentFavorites = array_filter(explode(',', $currentFavoritesStr));
|
|
||||||
|
|
||||||
$isFav = in_array($validated['language'], $currentFavorites);
|
|
||||||
if (!$isFav && $validated['active']) {
|
|
||||||
$currentFavorites[] = $validated['language'];
|
|
||||||
} elseif ($isFav && !$validated['active']) {
|
|
||||||
$index = array_search($validated['language'], $currentFavorites);
|
|
||||||
array_splice($currentFavorites, $index, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
setting()->putUser(user(), 'code-language-favourites', implode(',', $currentFavorites));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changed the stored preference for a list sort order.
|
|
||||||
*/
|
|
||||||
protected function changeListSort(int $userId, Request $request, string $listName)
|
|
||||||
{
|
|
||||||
$this->checkPermissionOrCurrentUser('users-manage', $userId);
|
|
||||||
|
|
||||||
$sort = $request->get('sort');
|
|
||||||
if (!in_array($sort, ['name', 'created_at', 'updated_at', 'default'])) {
|
|
||||||
$sort = 'name';
|
|
||||||
}
|
|
||||||
|
|
||||||
$order = $request->get('order');
|
|
||||||
if (!in_array($order, ['asc', 'desc'])) {
|
|
||||||
$order = 'asc';
|
|
||||||
}
|
|
||||||
|
|
||||||
$user = $this->userRepo->getById($userId);
|
|
||||||
$sortKey = $listName . '_sort';
|
|
||||||
$orderKey = $listName . '_sort_order';
|
|
||||||
setting()->putUser($user, $sortKey, $sort);
|
|
||||||
setting()->putUser($user, $orderKey, $order);
|
|
||||||
|
|
||||||
return redirect()->back(302, [], "/settings/users/$userId");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
134
app/Http/Controllers/UserPreferencesController.php
Normal file
134
app/Http/Controllers/UserPreferencesController.php
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BookStack\Http\Controllers;
|
||||||
|
|
||||||
|
use BookStack\Auth\UserRepo;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class UserPreferencesController extends Controller
|
||||||
|
{
|
||||||
|
protected UserRepo $userRepo;
|
||||||
|
|
||||||
|
public function __construct(UserRepo $userRepo)
|
||||||
|
{
|
||||||
|
$this->userRepo = $userRepo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the user's preferred book-list display setting.
|
||||||
|
*/
|
||||||
|
public function switchBooksView(Request $request, int $id)
|
||||||
|
{
|
||||||
|
return $this->switchViewType($id, $request, 'books');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the user's preferred shelf-list display setting.
|
||||||
|
*/
|
||||||
|
public function switchShelvesView(Request $request, int $id)
|
||||||
|
{
|
||||||
|
return $this->switchViewType($id, $request, 'bookshelves');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the user's preferred shelf-view book list display setting.
|
||||||
|
*/
|
||||||
|
public function switchShelfView(Request $request, int $id)
|
||||||
|
{
|
||||||
|
return $this->switchViewType($id, $request, 'bookshelf');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* For a type of list, switch with stored view type for a user.
|
||||||
|
*/
|
||||||
|
protected function switchViewType(int $userId, Request $request, string $listName)
|
||||||
|
{
|
||||||
|
$this->checkPermissionOrCurrentUser('users-manage', $userId);
|
||||||
|
|
||||||
|
$viewType = $request->get('view_type');
|
||||||
|
if (!in_array($viewType, ['grid', 'list'])) {
|
||||||
|
$viewType = 'list';
|
||||||
|
}
|
||||||
|
|
||||||
|
$user = $this->userRepo->getById($userId);
|
||||||
|
$key = $listName . '_view_type';
|
||||||
|
setting()->putUser($user, $key, $viewType);
|
||||||
|
|
||||||
|
return redirect()->back(302, [], "/settings/users/$userId");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the stored sort type for a particular view.
|
||||||
|
*/
|
||||||
|
public function changeSort(Request $request, string $id, string $type)
|
||||||
|
{
|
||||||
|
$validSortTypes = ['books', 'bookshelves', 'shelf_books', 'users', 'roles', 'webhooks', 'tags', 'page_revisions'];
|
||||||
|
if (!in_array($type, $validSortTypes)) {
|
||||||
|
return redirect()->back(500);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->checkPermissionOrCurrentUser('users-manage', $id);
|
||||||
|
|
||||||
|
$sort = substr($request->get('sort') ?: 'name', 0, 50);
|
||||||
|
$order = $request->get('order') === 'desc' ? 'desc' : 'asc';
|
||||||
|
|
||||||
|
$user = $this->userRepo->getById($id);
|
||||||
|
$sortKey = $type . '_sort';
|
||||||
|
$orderKey = $type . '_sort_order';
|
||||||
|
setting()->putUser($user, $sortKey, $sort);
|
||||||
|
setting()->putUser($user, $orderKey, $order);
|
||||||
|
|
||||||
|
return redirect()->back(302, [], "/settings/users/{$id}");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggle dark mode for the current user.
|
||||||
|
*/
|
||||||
|
public function toggleDarkMode()
|
||||||
|
{
|
||||||
|
$enabled = setting()->getForCurrentUser('dark-mode-enabled', false);
|
||||||
|
setting()->putUser(user(), 'dark-mode-enabled', $enabled ? 'false' : 'true');
|
||||||
|
|
||||||
|
return redirect()->back();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the stored section expansion preference for the given user.
|
||||||
|
*/
|
||||||
|
public function updateExpansionPreference(Request $request, string $id, string $key)
|
||||||
|
{
|
||||||
|
$this->checkPermissionOrCurrentUser('users-manage', $id);
|
||||||
|
$keyWhitelist = ['home-details'];
|
||||||
|
if (!in_array($key, $keyWhitelist)) {
|
||||||
|
return response('Invalid key', 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
$newState = $request->get('expand', 'false');
|
||||||
|
|
||||||
|
$user = $this->userRepo->getById($id);
|
||||||
|
setting()->putUser($user, 'section_expansion#' . $key, $newState);
|
||||||
|
|
||||||
|
return response('', 204);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateCodeLanguageFavourite(Request $request)
|
||||||
|
{
|
||||||
|
$validated = $this->validate($request, [
|
||||||
|
'language' => ['required', 'string', 'max:20'],
|
||||||
|
'active' => ['required', 'bool'],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$currentFavoritesStr = setting()->getForCurrentUser('code-language-favourites', '');
|
||||||
|
$currentFavorites = array_filter(explode(',', $currentFavoritesStr));
|
||||||
|
|
||||||
|
$isFav = in_array($validated['language'], $currentFavorites);
|
||||||
|
if (!$isFav && $validated['active']) {
|
||||||
|
$currentFavorites[] = $validated['language'];
|
||||||
|
} elseif ($isFav && !$validated['active']) {
|
||||||
|
$index = array_search($validated['language'], $currentFavorites);
|
||||||
|
array_splice($currentFavorites, $index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
setting()->putUser(user(), 'code-language-favourites', implode(',', $currentFavorites));
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,9 @@
|
||||||
namespace BookStack\Http\Controllers;
|
namespace BookStack\Http\Controllers;
|
||||||
|
|
||||||
use BookStack\Actions\ActivityType;
|
use BookStack\Actions\ActivityType;
|
||||||
|
use BookStack\Actions\Queries\WebhooksAllPaginatedAndSorted;
|
||||||
use BookStack\Actions\Webhook;
|
use BookStack\Actions\Webhook;
|
||||||
|
use BookStack\Util\SimpleListOptions;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
class WebhookController extends Controller
|
class WebhookController extends Controller
|
||||||
|
@ -18,16 +20,25 @@ class WebhookController extends Controller
|
||||||
/**
|
/**
|
||||||
* Show all webhooks configured in the system.
|
* Show all webhooks configured in the system.
|
||||||
*/
|
*/
|
||||||
public function index()
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$webhooks = Webhook::query()
|
$listOptions = SimpleListOptions::fromRequest($request, 'webhooks')->withSortOptions([
|
||||||
->orderBy('name', 'desc')
|
'name' => trans('common.sort_name'),
|
||||||
->with('trackedEvents')
|
'endpoint' => trans('settings.webhooks_endpoint'),
|
||||||
->get();
|
'created_at' => trans('common.sort_created_at'),
|
||||||
|
'updated_at' => trans('common.sort_updated_at'),
|
||||||
|
'active' => trans('common.status'),
|
||||||
|
]);
|
||||||
|
|
||||||
|
$webhooks = (new WebhooksAllPaginatedAndSorted())->run(20, $listOptions);
|
||||||
|
$webhooks->appends($listOptions->getPaginationAppends());
|
||||||
|
|
||||||
$this->setPageTitle(trans('settings.webhooks'));
|
$this->setPageTitle(trans('settings.webhooks'));
|
||||||
|
|
||||||
return view('settings.webhooks.index', ['webhooks' => $webhooks]);
|
return view('settings.webhooks.index', [
|
||||||
|
'webhooks' => $webhooks,
|
||||||
|
'listOptions' => $listOptions,
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
104
app/Util/SimpleListOptions.php
Normal file
104
app/Util/SimpleListOptions.php
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BookStack\Util;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handled options commonly used for item lists within the system, providing a standard
|
||||||
|
* model for handling and validating sort, order and search options.
|
||||||
|
*/
|
||||||
|
class SimpleListOptions
|
||||||
|
{
|
||||||
|
protected string $typeKey;
|
||||||
|
protected string $sort;
|
||||||
|
protected string $order;
|
||||||
|
protected string $search;
|
||||||
|
protected array $sortOptions = [];
|
||||||
|
|
||||||
|
public function __construct(string $typeKey, string $sort, string $order, string $search = '')
|
||||||
|
{
|
||||||
|
$this->typeKey = $typeKey;
|
||||||
|
$this->sort = $sort;
|
||||||
|
$this->order = $order;
|
||||||
|
$this->search = $search;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance from the given request.
|
||||||
|
* Takes the item type (plural) that's used as a key for storing sort preferences.
|
||||||
|
*/
|
||||||
|
public static function fromRequest(Request $request, string $typeKey, bool $sortDescDefault = false): self
|
||||||
|
{
|
||||||
|
$search = $request->get('search', '');
|
||||||
|
$sort = setting()->getForCurrentUser($typeKey . '_sort', '');
|
||||||
|
$order = setting()->getForCurrentUser($typeKey . '_sort_order', $sortDescDefault ? 'desc' : 'asc');
|
||||||
|
|
||||||
|
return new self($typeKey, $sort, $order, $search);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configure the valid sort options for this set of list options.
|
||||||
|
* Provided sort options must be an array, keyed by search properties
|
||||||
|
* with values being user-visible option labels.
|
||||||
|
* Returns current options for easy fluent usage during creation.
|
||||||
|
*/
|
||||||
|
public function withSortOptions(array $sortOptions): self
|
||||||
|
{
|
||||||
|
$this->sortOptions = array_merge($this->sortOptions, $sortOptions);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current order option.
|
||||||
|
*/
|
||||||
|
public function getOrder(): string
|
||||||
|
{
|
||||||
|
return strtolower($this->order) === 'desc' ? 'desc' : 'asc';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current sort option.
|
||||||
|
*/
|
||||||
|
public function getSort(): string
|
||||||
|
{
|
||||||
|
$default = array_key_first($this->sortOptions) ?? 'name';
|
||||||
|
$sort = $this->sort ?: $default;
|
||||||
|
|
||||||
|
if (empty($this->sortOptions) || array_key_exists($sort, $this->sortOptions)) {
|
||||||
|
return $sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the set search term.
|
||||||
|
*/
|
||||||
|
public function getSearch(): string
|
||||||
|
{
|
||||||
|
return $this->search;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the data to append for pagination.
|
||||||
|
*/
|
||||||
|
public function getPaginationAppends(): array
|
||||||
|
{
|
||||||
|
return ['search' => $this->search];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the data required by the sort control view.
|
||||||
|
*/
|
||||||
|
public function getSortControlData(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'options' => $this->sortOptions,
|
||||||
|
'order' => $this->getOrder(),
|
||||||
|
'sort' => $this->getSort(),
|
||||||
|
'type' => $this->typeKey,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,7 +62,7 @@ class EntityPermissions {
|
||||||
}
|
}
|
||||||
|
|
||||||
removeRowOnButtonClick(button) {
|
removeRowOnButtonClick(button) {
|
||||||
const row = button.closest('.content-permissions-row');
|
const row = button.closest('.item-list-row');
|
||||||
const roleId = button.dataset.roleId;
|
const roleId = button.dataset.roleId;
|
||||||
const roleName = button.dataset.roleName;
|
const roleName = button.dataset.roleName;
|
||||||
|
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
/**
|
/**
|
||||||
* ListSortControl
|
* ListSortControl
|
||||||
* Manages the logic for the control which provides list sorting options.
|
* Manages the logic for the control which provides list sorting options.
|
||||||
|
* @extends {Component}
|
||||||
*/
|
*/
|
||||||
class ListSortControl {
|
class ListSortControl {
|
||||||
|
|
||||||
constructor(elem) {
|
setup() {
|
||||||
this.elem = elem;
|
this.elem = this.$el;
|
||||||
this.menu = elem.querySelector('ul');
|
this.menu = this.$refs.menu;
|
||||||
|
|
||||||
this.sortInput = elem.querySelector('[name="sort"]');
|
this.sortInput = this.$refs.sort;
|
||||||
this.orderInput = elem.querySelector('[name="order"]');
|
this.orderInput = this.$refs.order;
|
||||||
this.form = elem.querySelector('form');
|
this.form = this.$refs.form;
|
||||||
|
|
||||||
|
this.setupListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupListeners() {
|
||||||
this.menu.addEventListener('click', event => {
|
this.menu.addEventListener('click', event => {
|
||||||
if (event.target.closest('[data-sort-value]') !== null) {
|
if (event.target.closest('[data-sort-value]') !== null) {
|
||||||
this.sortOptionClick(event);
|
this.sortOptionClick(event);
|
||||||
|
@ -34,8 +39,7 @@ class ListSortControl {
|
||||||
|
|
||||||
sortDirectionClick(event) {
|
sortDirectionClick(event) {
|
||||||
const currentDir = this.orderInput.value;
|
const currentDir = this.orderInput.value;
|
||||||
const newDir = (currentDir === 'asc') ? 'desc' : 'asc';
|
this.orderInput.value = (currentDir === 'asc') ? 'desc' : 'asc';
|
||||||
this.orderInput.value = newDir;
|
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.form.submit();
|
this.form.submit();
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,8 @@ class PermissionsTable {
|
||||||
|
|
||||||
setup() {
|
setup() {
|
||||||
this.container = this.$el;
|
this.container = this.$el;
|
||||||
|
this.cellSelector = this.$opts.cellSelector || 'td,th';
|
||||||
|
this.rowSelector = this.$opts.rowSelector || 'tr';
|
||||||
|
|
||||||
// Handle toggle all event
|
// Handle toggle all event
|
||||||
for (const toggleAllElem of (this.$manyRefs.toggleAll || [])) {
|
for (const toggleAllElem of (this.$manyRefs.toggleAll || [])) {
|
||||||
|
@ -27,15 +29,15 @@ class PermissionsTable {
|
||||||
|
|
||||||
toggleRowClick(event) {
|
toggleRowClick(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
this.toggleAllInElement(event.target.closest('tr'));
|
this.toggleAllInElement(event.target.closest(this.rowSelector));
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleColumnClick(event) {
|
toggleColumnClick(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
const tableCell = event.target.closest('th,td');
|
const tableCell = event.target.closest(this.cellSelector);
|
||||||
const colIndex = Array.from(tableCell.parentElement.children).indexOf(tableCell);
|
const colIndex = Array.from(tableCell.parentElement.children).indexOf(tableCell);
|
||||||
const tableRows = tableCell.closest('table').querySelectorAll('tr');
|
const tableRows = this.container.querySelectorAll(this.rowSelector);
|
||||||
const inputsToToggle = [];
|
const inputsToToggle = [];
|
||||||
|
|
||||||
for (let row of tableRows) {
|
for (let row of tableRows) {
|
||||||
|
|
|
@ -233,12 +233,14 @@ return [
|
||||||
'pages_permissions_success' => 'Page permissions updated',
|
'pages_permissions_success' => 'Page permissions updated',
|
||||||
'pages_revision' => 'Revision',
|
'pages_revision' => 'Revision',
|
||||||
'pages_revisions' => 'Page Revisions',
|
'pages_revisions' => 'Page Revisions',
|
||||||
|
'pages_revisions_desc' => 'Listed below are all the past revisions of this page. You can look back upon, compare, and restore old page versions if permissions allow. The full history of the page may not be fully reflected here since, depending on system configuration, old revisions could be auto-deleted.',
|
||||||
'pages_revisions_named' => 'Page Revisions for :pageName',
|
'pages_revisions_named' => 'Page Revisions for :pageName',
|
||||||
'pages_revision_named' => 'Page Revision for :pageName',
|
'pages_revision_named' => 'Page Revision for :pageName',
|
||||||
'pages_revision_restored_from' => 'Restored from #:id; :summary',
|
'pages_revision_restored_from' => 'Restored from #:id; :summary',
|
||||||
'pages_revisions_created_by' => 'Created By',
|
'pages_revisions_created_by' => 'Created By',
|
||||||
'pages_revisions_date' => 'Revision Date',
|
'pages_revisions_date' => 'Revision Date',
|
||||||
'pages_revisions_number' => '#',
|
'pages_revisions_number' => '#',
|
||||||
|
'pages_revisions_sort_number' => 'Revision Number',
|
||||||
'pages_revisions_numbered' => 'Revision #:id',
|
'pages_revisions_numbered' => 'Revision #:id',
|
||||||
'pages_revisions_numbered_changes' => 'Revision #:id Changes',
|
'pages_revisions_numbered_changes' => 'Revision #:id Changes',
|
||||||
'pages_revisions_editor' => 'Editor Type',
|
'pages_revisions_editor' => 'Editor Type',
|
||||||
|
@ -275,6 +277,7 @@ return [
|
||||||
'shelf_tags' => 'Shelf Tags',
|
'shelf_tags' => 'Shelf Tags',
|
||||||
'tag' => 'Tag',
|
'tag' => 'Tag',
|
||||||
'tags' => 'Tags',
|
'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_name' => 'Tag Name',
|
||||||
'tag_value' => 'Tag Value (Optional)',
|
'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.",
|
'tags_explain' => "Add some tags to better categorise your content. \n You can assign a value to a tag for more in-depth organisation.",
|
||||||
|
|
|
@ -133,6 +133,11 @@ return [
|
||||||
// Role Settings
|
// Role Settings
|
||||||
'roles' => 'Roles',
|
'roles' => 'Roles',
|
||||||
'role_user_roles' => 'User Roles',
|
'role_user_roles' => 'User Roles',
|
||||||
|
'roles_index_desc' => 'Roles are used to group users & provide system permission to their members. When a user is a member of multiple roles the privileges granted will stack and the user will inherit all abilities.',
|
||||||
|
'roles_x_users_assigned' => '1 user assigned|:count users assigned',
|
||||||
|
'roles_x_permissions_provided' => '1 permission|:count permissions',
|
||||||
|
'roles_assigned_users' => 'Assigned Users',
|
||||||
|
'roles_permissions_provided' => 'Provided Permissions',
|
||||||
'role_create' => 'Create New Role',
|
'role_create' => 'Create New Role',
|
||||||
'role_create_success' => 'Role successfully created',
|
'role_create_success' => 'Role successfully created',
|
||||||
'role_delete' => 'Delete Role',
|
'role_delete' => 'Delete Role',
|
||||||
|
@ -172,6 +177,7 @@ return [
|
||||||
|
|
||||||
// Users
|
// Users
|
||||||
'users' => 'Users',
|
'users' => 'Users',
|
||||||
|
'users_index_desc' => 'Create & manage individual user accounts within the system. User accounts are used for login and attribution of content & activity. Access permissions are primarily role-based but user content ownership, among other factors, may also affect permissions & access.',
|
||||||
'user_profile' => 'User Profile',
|
'user_profile' => 'User Profile',
|
||||||
'users_add_new' => 'Add New User',
|
'users_add_new' => 'Add New User',
|
||||||
'users_search' => 'Search Users',
|
'users_search' => 'Search Users',
|
||||||
|
@ -241,6 +247,8 @@ return [
|
||||||
|
|
||||||
// Webhooks
|
// Webhooks
|
||||||
'webhooks' => 'Webhooks',
|
'webhooks' => 'Webhooks',
|
||||||
|
'webhooks_index_desc' => 'Webhooks are a way to send data to external URLs when certain actions and events occur within the system which allows event-based integration with external platforms such as messaging or notification systems.',
|
||||||
|
'webhooks_x_trigger_events' => '1 trigger event|:count trigger events',
|
||||||
'webhooks_create' => 'Create New Webhook',
|
'webhooks_create' => 'Create New Webhook',
|
||||||
'webhooks_none_created' => 'No webhooks have yet been created.',
|
'webhooks_none_created' => 'No webhooks have yet been created.',
|
||||||
'webhooks_edit' => 'Edit Webhook',
|
'webhooks_edit' => 'Edit Webhook',
|
||||||
|
|
|
@ -286,35 +286,10 @@
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
td .tag-item {
|
.item-list-row .tag-item {
|
||||||
margin-bottom: 0;
|
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
|
* API Docs
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -798,37 +798,6 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.content-permissions {
|
|
||||||
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
.content-permissions-row {
|
|
||||||
border: 1.5px solid;
|
|
||||||
@include lightDark(border-color, #E2E2E2, #444);
|
|
||||||
border-bottom-width: 0;
|
|
||||||
label {
|
|
||||||
padding-bottom: 0;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
@include lightDark(background-color, #F2F2F2, #333);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.content-permissions-row:first-child {
|
|
||||||
border-radius: 4px 4px 0 0;
|
|
||||||
}
|
|
||||||
.content-permissions-row:last-child {
|
|
||||||
border-radius: 0 0 4px 4px;
|
|
||||||
border-bottom-width: 1.5px;
|
|
||||||
}
|
|
||||||
.content-permissions-row:first-child:last-child {
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
.content-permissions-row-toggle-all {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
.content-permissions-row:hover .content-permissions-row-toggle-all {
|
|
||||||
visibility: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
.template-item {
|
.template-item {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
@ -969,4 +938,48 @@ body.flexbox-support #entity-selector-wrap .popup-body .form-group {
|
||||||
.dropdown-search-dropdown .dropdown-search-list {
|
.dropdown-search-dropdown .dropdown-search-list {
|
||||||
max-height: 240px;
|
max-height: 240px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-list {
|
||||||
|
box-shadow: 0 1px 3px 0 rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
.item-list-row {
|
||||||
|
border: 1.5px solid;
|
||||||
|
@include lightDark(border-color, #E2E2E2, #444);
|
||||||
|
border-bottom-width: 0;
|
||||||
|
label {
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
@include lightDark(background-color, #F6F6F6, #333);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.item-list-row:first-child {
|
||||||
|
border-radius: 4px 4px 0 0;
|
||||||
|
}
|
||||||
|
.item-list-row:last-child {
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
border-bottom-width: 1.5px;
|
||||||
|
}
|
||||||
|
.item-list-row:first-child:last-child {
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
.item-list-row-toggle-all {
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
.item-list-row:hover .item-list-row-toggle-all {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-indicator-active, .status-indicator-inactive {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
.status-indicator-active {
|
||||||
|
background-color: $positive;
|
||||||
|
}
|
||||||
|
.status-indicator-inactive {
|
||||||
|
background-color: $negative;
|
||||||
}
|
}
|
|
@ -144,6 +144,10 @@ body.flexbox {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.flex-container-row.inline, .flex-container-column.inline {
|
||||||
|
display: inline-flex !important;
|
||||||
|
}
|
||||||
|
|
||||||
.flex-container-column.wrap, .flex-container-row.wrap {
|
.flex-container-column.wrap, .flex-container-row.wrap {
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
}
|
}
|
||||||
|
@ -156,6 +160,23 @@ body.flexbox {
|
||||||
flex-basis: auto;
|
flex-basis: auto;
|
||||||
flex-grow: 0;
|
flex-grow: 0;
|
||||||
}
|
}
|
||||||
|
&.fill-area {
|
||||||
|
flex-grow: 1;
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-width: fit-content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-2 {
|
||||||
|
min-height: 0;
|
||||||
|
flex: 2;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex-3 {
|
||||||
|
min-height: 0;
|
||||||
|
flex: 3;
|
||||||
|
max-width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.flex-none {
|
.flex-none {
|
||||||
|
@ -178,6 +199,36 @@ body.flexbox {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Min width utilities
|
||||||
|
*/
|
||||||
|
.min-width-xxxxs {
|
||||||
|
min-width: 60px;
|
||||||
|
}
|
||||||
|
.min-width-xxxs {
|
||||||
|
min-width: 80px;
|
||||||
|
}
|
||||||
|
.min-width-xxs {
|
||||||
|
min-width: 100px;
|
||||||
|
}
|
||||||
|
.min-width-xs {
|
||||||
|
min-width: 120px;
|
||||||
|
}
|
||||||
|
.min-width-s {
|
||||||
|
min-width: 160px;
|
||||||
|
}
|
||||||
|
.min-width-m {
|
||||||
|
min-width: 200px;
|
||||||
|
}
|
||||||
|
.min-width-l {
|
||||||
|
min-width: 240px;
|
||||||
|
}
|
||||||
|
.min-width-xl {
|
||||||
|
min-width: 280px;
|
||||||
|
}
|
||||||
|
.min-width-xxl {
|
||||||
|
min-width: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display and float utilities
|
* Display and float utilities
|
||||||
|
|
28
resources/sass/_opacity.scss
Normal file
28
resources/sass/_opacity.scss
Normal 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;
|
||||||
|
}
|
|
@ -4,6 +4,7 @@
|
||||||
@import "variables";
|
@import "variables";
|
||||||
@import "mixins";
|
@import "mixins";
|
||||||
@import "spacing";
|
@import "spacing";
|
||||||
|
@import "opacity";
|
||||||
@import "html";
|
@import "html";
|
||||||
@import "text";
|
@import "text";
|
||||||
@import "colors";
|
@import "colors";
|
||||||
|
@ -352,15 +353,4 @@ input.scroll-box-search, .scroll-box-header-item {
|
||||||
transform: rotate(180deg);
|
transform: rotate(180deg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
table.table .table-user-item {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 42px 1fr;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
table.table .table-entity-item {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 36px 1fr;
|
|
||||||
align-items: center;
|
|
||||||
}
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
@extends('layouts.tri')
|
@extends('layouts.tri')
|
||||||
|
|
||||||
@section('body')
|
@section('body')
|
||||||
@include('books.parts.list', ['books' => $books, 'view' => $view])
|
@include('books.parts.list', ['books' => $books, 'view' => $view, 'listOptions' => $listOptions])
|
||||||
@stop
|
@stop
|
||||||
|
|
||||||
@section('left')
|
@section('left')
|
||||||
|
|
|
@ -2,13 +2,7 @@
|
||||||
<div class="grid half v-center no-row-gap">
|
<div class="grid half v-center no-row-gap">
|
||||||
<h1 class="list-heading">{{ trans('entities.books') }}</h1>
|
<h1 class="list-heading">{{ trans('entities.books') }}</h1>
|
||||||
<div class="text-m-right my-m">
|
<div class="text-m-right my-m">
|
||||||
|
@include('common.sort', $listOptions->getSortControlData())
|
||||||
@include('entities.sort', ['options' => [
|
|
||||||
'name' => trans('common.sort_name'),
|
|
||||||
'created_at' => trans('common.sort_created_at'),
|
|
||||||
'updated_at' => trans('common.sort_updated_at'),
|
|
||||||
], 'order' => $order, 'sort' => $sort, 'type' => 'books'])
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if(count($books) > 0)
|
@if(count($books) > 0)
|
||||||
|
@ -19,11 +13,11 @@
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="grid third">
|
<div class="grid third">
|
||||||
@foreach($books as $key => $book)
|
@foreach($books as $key => $book)
|
||||||
@include('entities.grid-item', ['entity' => $book])
|
@include('entities.grid-item', ['entity' => $book])
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
<div>
|
<div>
|
||||||
{!! $books->render() !!}
|
{!! $books->render() !!}
|
||||||
|
|
|
@ -2,25 +2,40 @@
|
||||||
$selectedSort = (isset($sort) && array_key_exists($sort, $options)) ? $sort : array_keys($options)[0];
|
$selectedSort = (isset($sort) && array_key_exists($sort, $options)) ? $sort : array_keys($options)[0];
|
||||||
$order = (isset($order) && in_array($order, ['asc', 'desc'])) ? $order : 'asc';
|
$order = (isset($order) && in_array($order, ['asc', 'desc'])) ? $order : 'asc';
|
||||||
?>
|
?>
|
||||||
<div class="list-sort-container" list-sort-control>
|
<div component="list-sort-control" class="list-sort-container">
|
||||||
<div class="list-sort-label">{{ trans('common.sort') }}</div>
|
<div class="list-sort-label">{{ trans('common.sort') }}</div>
|
||||||
<form action="{{ url("/settings/users/". user()->id ."/change-sort/{$type}") }}" method="post">
|
<form refs="list-sort-control@form"
|
||||||
|
@if($useQuery ?? false)
|
||||||
|
action="{{ url()->current() }}"
|
||||||
|
method="get"
|
||||||
|
@else
|
||||||
|
action="{{ url("/settings/users/". user()->id ."/change-sort/{$type}") }}"
|
||||||
|
method="post"
|
||||||
|
@endif
|
||||||
|
>
|
||||||
|
|
||||||
{!! csrf_field() !!}
|
@if($useQuery ?? false)
|
||||||
{!! method_field('PATCH') !!}
|
@foreach(array_filter(request()->except(['sort', 'order'])) as $key => $value)
|
||||||
<input type="hidden" value="{{ $selectedSort }}" name="sort">
|
<input type="hidden" name="{{ $key }}" value="{{ $value }}">
|
||||||
<input type="hidden" value="{{ $order }}" name="order">
|
@endforeach
|
||||||
|
@else
|
||||||
|
{!! method_field('PATCH') !!}
|
||||||
|
{!! csrf_field() !!}
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<input refs="list-sort-control@sort" type="hidden" value="{{ $selectedSort }}" name="sort">
|
||||||
|
<input refs="list-sort-control@order" type="hidden" value="{{ $order }}" name="order">
|
||||||
|
|
||||||
<div class="list-sort">
|
<div class="list-sort">
|
||||||
<div component="dropdown" class="list-sort-type dropdown-container">
|
<div component="dropdown" class="list-sort-type dropdown-container">
|
||||||
<div refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.sort_options') }}" tabindex="0">{{ $options[$selectedSort] }}</div>
|
<div refs="dropdown@toggle" aria-haspopup="true" aria-expanded="false" aria-label="{{ trans('common.sort_options') }}" tabindex="0">{{ $options[$selectedSort] }}</div>
|
||||||
<ul refs="dropdown@menu" class="dropdown-menu">
|
<ul refs="dropdown@menu list-sort-control@menu" class="dropdown-menu">
|
||||||
@foreach($options as $key => $label)
|
@foreach($options as $key => $label)
|
||||||
<li @if($key === $selectedSort) class="active" @endif><a href="#" data-sort-value="{{$key}}" class="text-item">{{ $label }}</a></li>
|
<li @if($key === $selectedSort) class="active" @endif><a href="#" data-sort-value="{{$key}}" class="text-item">{{ $label }}</a></li>
|
||||||
@endforeach
|
@endforeach
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<button href="#" class="list-sort-dir" type="button" data-sort-dir
|
<button class="list-sort-dir" type="button" data-sort-dir
|
||||||
aria-label="{{ trans('common.sort_direction_toggle') }} - {{ $order === 'asc' ? trans('common.sort_ascending') : trans('common.sort_descending') }}" tabindex="0">
|
aria-label="{{ trans('common.sort_direction_toggle') }} - {{ $order === 'asc' ? trans('common.sort_ascending') : trans('common.sort_descending') }}" tabindex="0">
|
||||||
@icon($order === 'desc' ? 'sort-up' : 'sort-down')
|
@icon($order === 'desc' ? 'sort-up' : 'sort-down')
|
||||||
</button>
|
</button>
|
3
resources/views/common/status-indicator.blade.php
Normal file
3
resources/views/common/status-indicator.blade.php
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<span title="{{ trans('common.status_' . ($status ? 'active' : 'inactive')) }}"
|
||||||
|
class="status-indicator-{{ $status ? 'active' : 'inactive' }}"
|
||||||
|
></span>
|
|
@ -5,7 +5,7 @@ $permission - The entity permission containing the permissions.
|
||||||
$inheriting - Boolean if the current row should be marked as inheriting default permissions. Used for "Everyone Else" role.
|
$inheriting - Boolean if the current row should be marked as inheriting default permissions. Used for "Everyone Else" role.
|
||||||
--}}
|
--}}
|
||||||
|
|
||||||
<div component="permissions-table" class="content-permissions-row flex-container-row justify-space-between wrap">
|
<div component="permissions-table" class="item-list-row flex-container-row justify-space-between wrap">
|
||||||
<div class="gap-x-m flex-container-row items-center px-l py-m flex">
|
<div class="gap-x-m flex-container-row items-center px-l py-m flex">
|
||||||
<div class="text-large" title="{{ $role->id === 0 ? trans('entities.permissions_role_everyone_else') : trans('common.role') }}">
|
<div class="text-large" title="{{ $role->id === 0 ? trans('entities.permissions_role_everyone_else') : trans('common.role') }}">
|
||||||
@icon($role->id === 0 ? 'groups' : 'role')
|
@icon($role->id === 0 ? 'groups' : 'role')
|
||||||
|
@ -16,7 +16,7 @@ $inheriting - Boolean if the current row should be marked as inheriting default
|
||||||
</span>
|
</span>
|
||||||
@if($role->id !== 0)
|
@if($role->id !== 0)
|
||||||
<button type="button"
|
<button type="button"
|
||||||
class="ml-auto flex-none text-small text-primary text-button hover-underline content-permissions-row-toggle-all hide-under-s"
|
class="ml-auto flex-none text-small text-primary text-button hover-underline item-list-row-toggle-all hide-under-s"
|
||||||
refs="permissions-table@toggle-all"
|
refs="permissions-table@toggle-all"
|
||||||
><strong>{{ trans('common.toggle_all') }}</strong></button>
|
><strong>{{ trans('common.toggle_all') }}</strong></button>
|
||||||
@endif
|
@endif
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
<div refs="entity-permissions@role-container" class="content-permissions mt-m mb-m">
|
<div refs="entity-permissions@role-container" class="item-list mt-m mb-m">
|
||||||
@foreach($data->permissionsWithRoles() as $permission)
|
@foreach($data->permissionsWithRoles() as $permission)
|
||||||
@include('form.entity-permissions-row', [
|
@include('form.entity-permissions-row', [
|
||||||
'permission' => $permission,
|
'permission' => $permission,
|
||||||
|
@ -58,7 +58,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="content-permissions mt-m mb-xl">
|
<div class="item-list mt-m mb-xl">
|
||||||
@include('form.entity-permissions-row', [
|
@include('form.entity-permissions-row', [
|
||||||
'role' => $data->everyoneElseRole(),
|
'role' => $data->everyoneElseRole(),
|
||||||
'permission' => $data->everyoneElseEntityPermission(),
|
'permission' => $data->everyoneElseEntityPermission(),
|
||||||
|
|
|
@ -1,38 +1,43 @@
|
||||||
<tr>
|
<div class="item-list-row flex-container-row items-center wrap">
|
||||||
<td>{{ $revision->revision_number == 0 ? '' : $revision->revision_number }}</td>
|
<div class="flex fit-content min-width-xxxxs px-m py-xs">
|
||||||
<td>
|
<span class="hide-over-l">{{ trans('entities.pages_revisions_number') }}</span>
|
||||||
|
{{ $revision->revision_number == 0 ? '' : $revision->revision_number }}
|
||||||
|
</div>
|
||||||
|
<div class="flex-2 px-m py-xs min-width-s">
|
||||||
{{ $revision->name }}
|
{{ $revision->name }}
|
||||||
<br>
|
<br>
|
||||||
<small class="text-muted">({{ $revision->is_markdown ? 'Markdown' : 'WYSIWYG' }})</small>
|
<small class="text-muted">(<strong class="hide-over-l">{{ trans('entities.pages_revisions_editor') }}: </strong>{{ $revision->is_markdown ? 'Markdown' : 'WYSIWYG' }})</small>
|
||||||
</td>
|
</div>
|
||||||
<td style="line-height: 0;" width="30">
|
<div class="flex-3 px-m py-xs min-width-l">
|
||||||
@if($revision->createdBy)
|
<div class="flex-container-row items-center gap-s">
|
||||||
<img class="avatar" src="{{ $revision->createdBy->getAvatar(30) }}" alt="{{ $revision->createdBy->name }}">
|
@if($revision->createdBy)
|
||||||
@endif
|
<img class="avatar flex-none" height="30" width="30" src="{{ $revision->createdBy->getAvatar(30) }}" alt="{{ $revision->createdBy->name }}">
|
||||||
</td>
|
@endif
|
||||||
<td width="260">
|
<div>
|
||||||
@if($revision->createdBy) {{ $revision->createdBy->name }} @else {{ trans('common.deleted_user') }} @endif
|
@if($revision->createdBy) {{ $revision->createdBy->name }} @else {{ trans('common.deleted_user') }} @endif
|
||||||
<br>
|
<br>
|
||||||
<div class="text-muted">
|
<div class="text-muted">
|
||||||
<small>{{ $revision->created_at->formatLocalized('%e %B %Y %H:%M:%S') }}</small>
|
<small>{{ $revision->created_at->formatLocalized('%e %B %Y %H:%M:%S') }}</small>
|
||||||
<small>({{ $revision->created_at->diffForHumans() }})</small>
|
<small>({{ $revision->created_at->diffForHumans() }})</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
<td>
|
<div class="flex-2 px-m py-xs min-width-m text-small">
|
||||||
{{ $revision->summary }}
|
{{ $revision->summary }}
|
||||||
</td>
|
</div>
|
||||||
<td class="actions text-small text-right">
|
<div class="flex-2 px-m py-xs actions text-small text-l-right min-width-l">
|
||||||
<a href="{{ $revision->getUrl('changes') }}" target="_blank" rel="noopener">{{ trans('entities.pages_revisions_changes') }}</a>
|
<a href="{{ $revision->getUrl('changes') }}" target="_blank" rel="noopener">{{ trans('entities.pages_revisions_changes') }}</a>
|
||||||
<span class="text-muted"> | </span>
|
<span class="text-muted opacity-70"> | </span>
|
||||||
|
|
||||||
|
|
||||||
@if ($index === 0)
|
@if ($current)
|
||||||
<a target="_blank" rel="noopener" href="{{ $revision->page->getUrl() }}"><i>{{ trans('entities.pages_revisions_current') }}</i></a>
|
<a target="_blank" rel="noopener" href="{{ $revision->page->getUrl() }}"><i>{{ trans('entities.pages_revisions_current') }}</i></a>
|
||||||
@else
|
@else
|
||||||
<a href="{{ $revision->getUrl() }}" target="_blank" rel="noopener">{{ trans('entities.pages_revisions_preview') }}</a>
|
<a href="{{ $revision->getUrl() }}" target="_blank" rel="noopener">{{ trans('entities.pages_revisions_preview') }}</a>
|
||||||
|
|
||||||
@if(userCan('page-update', $revision->page))
|
@if(userCan('page-update', $revision->page))
|
||||||
<span class="text-muted"> | </span>
|
<span class="text-muted opacity-70"> | </span>
|
||||||
<div component="dropdown" class="dropdown-container">
|
<div component="dropdown" class="dropdown-container">
|
||||||
<a refs="dropdown@toggle" href="#" aria-haspopup="true" aria-expanded="false">{{ trans('entities.pages_revisions_restore') }}</a>
|
<a refs="dropdown@toggle" href="#" aria-haspopup="true" aria-expanded="false">{{ trans('entities.pages_revisions_restore') }}</a>
|
||||||
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
|
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
|
||||||
|
@ -52,7 +57,7 @@
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if(userCan('page-delete', $revision->page))
|
@if(userCan('page-delete', $revision->page))
|
||||||
<span class="text-muted"> | </span>
|
<span class="text-muted opacity-70"> | </span>
|
||||||
<div component="dropdown" class="dropdown-container">
|
<div component="dropdown" class="dropdown-container">
|
||||||
<a refs="dropdown@toggle" href="#" aria-haspopup="true" aria-expanded="false">{{ trans('common.delete') }}</a>
|
<a refs="dropdown@toggle" href="#" aria-haspopup="true" aria-expanded="false">{{ trans('common.delete') }}</a>
|
||||||
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
|
<ul refs="dropdown@menu" class="dropdown-menu" role="menu">
|
||||||
|
@ -71,5 +76,5 @@
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
|
@ -17,26 +17,36 @@
|
||||||
|
|
||||||
<main class="card content-wrap">
|
<main class="card content-wrap">
|
||||||
<h1 class="list-heading">{{ trans('entities.pages_revisions') }}</h1>
|
<h1 class="list-heading">{{ trans('entities.pages_revisions') }}</h1>
|
||||||
|
|
||||||
|
<p class="text-muted">{{ trans('entities.pages_revisions_desc') }}</p>
|
||||||
|
|
||||||
|
<div class="flex-container-row my-m items-center justify-space-between wrap gap-x-m gap-y-s">
|
||||||
|
{{ $revisions->links() }}
|
||||||
|
<div>
|
||||||
|
@include('common.sort', $listOptions->getSortControlData())
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
@if(count($revisions) > 0)
|
@if(count($revisions) > 0)
|
||||||
|
<div class="item-list">
|
||||||
<table class="table">
|
<div class="item-list-row flex-container-row items-center strong hide-under-l">
|
||||||
<tr>
|
<div class="flex fit-content min-width-xxxxs px-m py-xs">{{ trans('entities.pages_revisions_number') }}</div>
|
||||||
<th width="56">{{ trans('entities.pages_revisions_number') }}</th>
|
<div class="flex-2 px-m py-xs">{{ trans('entities.pages_name') }} / {{ trans('entities.pages_revisions_editor') }}</div>
|
||||||
<th>
|
<div class="flex-3 px-m py-xs">{{ trans('entities.pages_revisions_created_by') }} / {{ trans('entities.pages_revisions_date') }}</div>
|
||||||
{{ trans('entities.pages_name') }} / {{ trans('entities.pages_revisions_editor') }}
|
<div class="flex-2 px-m py-xs">{{ trans('entities.pages_revisions_changelog') }}</div>
|
||||||
</th>
|
<div class="flex-2 px-m py-xs text-right">{{ trans('common.actions') }}</div>
|
||||||
<th colspan="2">{{ trans('entities.pages_revisions_created_by') }} / {{ trans('entities.pages_revisions_date') }}</th>
|
</div>
|
||||||
<th>{{ trans('entities.pages_revisions_changelog') }}</th>
|
|
||||||
<th class="text-right">{{ trans('common.actions') }}</th>
|
|
||||||
</tr>
|
|
||||||
@foreach($revisions as $index => $revision)
|
@foreach($revisions as $index => $revision)
|
||||||
@include('pages.parts.revision-table-row', ['revision' => $revision])
|
@include('pages.parts.revisions-index-row', ['revision' => $revision, 'current' => $page->revision_count === $revision->revision_number])
|
||||||
@endforeach
|
@endforeach
|
||||||
</table>
|
</div>
|
||||||
|
|
||||||
@else
|
@else
|
||||||
<p>{{ trans('entities.pages_revisions_none') }}</p>
|
<p>{{ trans('entities.pages_revisions_none') }}</p>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
<div class="my-m">
|
||||||
|
{{ $revisions->links() }}
|
||||||
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -2,25 +2,24 @@
|
||||||
@type - Type of term (exact, tag)
|
@type - Type of term (exact, tag)
|
||||||
@currentList
|
@currentList
|
||||||
--}}
|
--}}
|
||||||
<table component="add-remove-rows"
|
<div component="add-remove-rows"
|
||||||
option:add-remove-rows:remove-selector="button.text-neg"
|
option:add-remove-rows:remove-selector="button.text-neg"
|
||||||
option:add-remove-rows:row-selector="tr"
|
option:add-remove-rows:row-selector=".flex-container-row"
|
||||||
class="no-style">
|
class="flex-container-column gap-xs">
|
||||||
@foreach(array_merge($currentList, ['']) as $term)
|
@foreach(array_merge($currentList, ['']) as $term)
|
||||||
<tr @if(empty($term)) class="hidden" refs="add-remove-rows@model" @endif>
|
<div @if(empty($term)) refs="add-remove-rows@model" @endif
|
||||||
<td class="pb-s pr-m">
|
class="{{ $term ? '' : 'hidden' }} flex-container-row items-center gap-x-xs">
|
||||||
|
<div>
|
||||||
<input class="exact-input outline" type="text" name="{{$type}}[]" value="{{ $term }}">
|
<input class="exact-input outline" type="text" name="{{$type}}[]" value="{{ $term }}">
|
||||||
</td>
|
</div>
|
||||||
<td>
|
<div>
|
||||||
<button type="button" class="text-neg text-button">@icon('close')</button>
|
<button type="button" class="text-neg text-button icon-button p-xs">@icon('close')</button>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
<tr>
|
<div class="flex py-xs">
|
||||||
<td colspan="2">
|
<button refs="add-remove-rows@add" type="button" class="text-button">
|
||||||
<button refs="add-remove-rows@add" type="button" class="text-button">
|
@icon('add-circle'){{ trans('common.add') }}
|
||||||
@icon('add-circle'){{ trans('common.add') }}
|
</button>
|
||||||
</button>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
|
||||||
</table>
|
|
|
@ -9,7 +9,11 @@
|
||||||
<h1 class="list-heading">{{ trans('settings.audit') }}</h1>
|
<h1 class="list-heading">{{ trans('settings.audit') }}</h1>
|
||||||
<p class="text-muted">{{ trans('settings.audit_desc') }}</p>
|
<p class="text-muted">{{ trans('settings.audit_desc') }}</p>
|
||||||
|
|
||||||
<form action="{{ url('/settings/audit') }}" method="get" class="flex-container-row wrap justify-flex-start gap-m">
|
<form action="{{ url('/settings/audit') }}" method="get" class="flex-container-row wrap justify-flex-start gap-x-m gap-y-xs">
|
||||||
|
|
||||||
|
@foreach(request()->only(['order', 'sort']) as $key => $val)
|
||||||
|
<input type="hidden" name="{{ $key }}" value="{{ $val }}">
|
||||||
|
@endforeach
|
||||||
|
|
||||||
<div component="dropdown" class="list-sort-type dropdown-container">
|
<div component="dropdown" class="list-sort-type dropdown-container">
|
||||||
<label for="">{{ trans('settings.audit_event_filter') }}</label>
|
<label for="">{{ trans('settings.audit_event_filter') }}</label>
|
||||||
|
@ -18,17 +22,17 @@
|
||||||
aria-haspopup="true"
|
aria-haspopup="true"
|
||||||
aria-expanded="false"
|
aria-expanded="false"
|
||||||
aria-label="{{ trans('common.sort_options') }}"
|
aria-label="{{ trans('common.sort_options') }}"
|
||||||
class="input-base text-left">{{ $listDetails['event'] ?: trans('settings.audit_event_filter_no_filter') }}</button>
|
class="input-base text-left">{{ $filters['event'] ?: trans('settings.audit_event_filter_no_filter') }}</button>
|
||||||
<ul refs="dropdown@menu" class="dropdown-menu">
|
<ul refs="dropdown@menu" class="dropdown-menu">
|
||||||
<li @if($listDetails['event'] === '') class="active" @endif><a href="{{ sortUrl('/settings/audit', $listDetails, ['event' => '']) }}" class="text-item">{{ trans('settings.audit_event_filter_no_filter') }}</a></li>
|
<li @if($filters['event'] === '') class="active" @endif><a href="{{ sortUrl('/settings/audit', array_filter(request()->except('page')), ['event' => '']) }}" class="text-item">{{ trans('settings.audit_event_filter_no_filter') }}</a></li>
|
||||||
@foreach($activityTypes as $type)
|
@foreach($activityTypes as $type)
|
||||||
<li @if($type === $listDetails['event']) class="active" @endif><a href="{{ sortUrl('/settings/audit', $listDetails, ['event' => $type]) }}" class="text-item">{{ $type }}</a></li>
|
<li @if($type === $filters['event']) class="active" @endif><a href="{{ sortUrl('/settings/audit', array_filter(request()->except('page')), ['event' => $type]) }}" class="text-item">{{ $type }}</a></li>
|
||||||
@endforeach
|
@endforeach
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(!empty($listDetails['event']))
|
@if(!empty($filters['event']))
|
||||||
<input type="hidden" name="event" value="{{ $listDetails['event'] }}">
|
<input type="hidden" name="event" value="{{ $filters['event'] }}">
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@foreach(['date_from', 'date_to'] as $filterKey)
|
@foreach(['date_from', 'date_to'] as $filterKey)
|
||||||
|
@ -38,7 +42,7 @@
|
||||||
component="submit-on-change"
|
component="submit-on-change"
|
||||||
type="date"
|
type="date"
|
||||||
name="{{ $filterKey }}"
|
name="{{ $filterKey }}"
|
||||||
value="{{ $listDetails[$filterKey] ?? '' }}">
|
value="{{ $filters[$filterKey] ?? '' }}">
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
||||||
|
@ -46,64 +50,70 @@
|
||||||
component="submit-on-change"
|
component="submit-on-change"
|
||||||
option:submit-on-change:filter='[name="user"]'>
|
option:submit-on-change:filter='[name="user"]'>
|
||||||
<label for="owner">{{ trans('settings.audit_table_user') }}</label>
|
<label for="owner">{{ trans('settings.audit_table_user') }}</label>
|
||||||
@include('form.user-select', ['user' => $listDetails['user'] ? \BookStack\Auth\User::query()->find($listDetails['user']) : null, 'name' => 'user'])
|
@include('form.user-select', ['user' => $filters['user'] ? \BookStack\Auth\User::query()->find($filters['user']) : null, 'name' => 'user'])
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="ip">{{ trans('settings.audit_table_ip') }}</label>
|
<label for="ip">{{ trans('settings.audit_table_ip') }}</label>
|
||||||
@include('form.text', ['name' => 'ip', 'model' => (object) $listDetails])
|
@include('form.text', ['name' => 'ip', 'model' => (object) $filters])
|
||||||
<input type="submit" style="display: none">
|
<input type="submit" style="display: none">
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<hr class="mt-l mb-s">
|
<hr class="mt-m mb-s">
|
||||||
|
|
||||||
{{ $activities->links() }}
|
<div class="flex-container-row justify-space-between items-center wrap">
|
||||||
|
<div class="flex-2 min-width-xl">{{ $activities->links() }}</div>
|
||||||
|
<div class="flex-none min-width-m py-m">
|
||||||
|
@include('common.sort', array_merge($listOptions->getSortControlData(), ['useQuery' => true]))
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<table class="table">
|
<div class="item-list">
|
||||||
<tbody>
|
<div class="item-list-row flex-container-row items-center bold hide-under-m">
|
||||||
<tr>
|
<div class="flex-2 px-m py-xs flex-container-row items-center">{{ trans('settings.audit_table_user') }}</div>
|
||||||
<th>{{ trans('settings.audit_table_user') }}</th>
|
<div class="flex-2 px-m py-xs">{{ trans('settings.audit_table_event') }}</div>
|
||||||
<th>
|
<div class="flex-3 px-m py-xs">{{ trans('settings.audit_table_related') }}</div>
|
||||||
<a href="{{ sortUrl('/settings/audit', $listDetails, ['sort' => 'key']) }}">{{ trans('settings.audit_table_event') }}</a>
|
<div class="flex-container-row flex-3">
|
||||||
</th>
|
<div class="flex px-m py-xs">{{ trans('settings.audit_table_ip') }}</div>
|
||||||
<th>{{ trans('settings.audit_table_related') }}</th>
|
<div class="flex-2 px-m py-xs text-right">{{ trans('settings.audit_table_date') }}</div>
|
||||||
<th>{{ trans('settings.audit_table_ip') }}</th>
|
</div>
|
||||||
<th>
|
</div>
|
||||||
<a href="{{ sortUrl('/settings/audit', $listDetails, ['sort' => 'created_at']) }}">{{ trans('settings.audit_table_date') }}</a></th>
|
|
||||||
</tr>
|
|
||||||
@foreach($activities as $activity)
|
@foreach($activities as $activity)
|
||||||
<tr>
|
<div class="item-list-row flex-container-row items-center wrap py-xxs">
|
||||||
<td>
|
<div class="flex-2 px-m py-xxs flex-container-row items-center min-width-m">
|
||||||
@include('settings.parts.table-user', ['user' => $activity->user, 'user_id' => $activity->user_id])
|
@include('settings.parts.table-user', ['user' => $activity->user, 'user_id' => $activity->user_id])
|
||||||
</td>
|
</div>
|
||||||
<td>{{ $activity->type }}</td>
|
<div class="flex-2 px-m py-xxs min-width-m"><strong class="mr-xs hide-over-m">{{ trans('settings.audit_table_event') }}:</strong> {{ $activity->type }}</div>
|
||||||
<td width="40%">
|
<div class="flex-3 px-m py-xxs min-width-l">
|
||||||
@if($activity->entity)
|
@if($activity->entity)
|
||||||
<a href="{{ $activity->entity->getUrl() }}" class="table-entity-item">
|
<a href="{{ $activity->entity->getUrl() }}" class="flex-container-row items-center">
|
||||||
<span role="presentation" class="icon text-{{$activity->entity->getType()}}">@icon($activity->entity->getType())</span>
|
<span role="presentation" class="icon flex-none text-{{$activity->entity->getType()}}">@icon($activity->entity->getType())</span>
|
||||||
<div class="text-{{ $activity->entity->getType() }}">
|
<div class="flex text-{{ $activity->entity->getType() }}">
|
||||||
{{ $activity->entity->name }}
|
{{ $activity->entity->name }}
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
@elseif($activity->detail && $activity->isForEntity())
|
@elseif($activity->detail && $activity->isForEntity())
|
||||||
<div class="px-m">
|
<div>
|
||||||
{{ trans('settings.audit_deleted_item') }} <br>
|
{{ trans('settings.audit_deleted_item') }} <br>
|
||||||
{{ trans('settings.audit_deleted_item_name', ['name' => $activity->detail]) }}
|
{{ trans('settings.audit_deleted_item_name', ['name' => $activity->detail]) }}
|
||||||
</div>
|
</div>
|
||||||
@elseif($activity->detail)
|
@elseif($activity->detail)
|
||||||
<div class="px-m">{{ $activity->detail }}</div>
|
<div>{{ $activity->detail }}</div>
|
||||||
@endif
|
@endif
|
||||||
</td>
|
</div>
|
||||||
<td>{{ $activity->ip }}</td>
|
<div class="flex-container-row flex-3">
|
||||||
<td>{{ $activity->created_at }}</td>
|
<div class="flex px-m py-xxs min-width-xs"><strong class="mr-xs hide-over-m">{{ trans('settings.audit_table_ip') }}:<br></strong> {{ $activity->ip }}</div>
|
||||||
</tr>
|
<div class="flex-2 px-m py-xxs text-m-right min-width-xs"><strong class="mr-xs hide-over-m">{{ trans('settings.audit_table_date') }}:<br></strong> {{ $activity->created_at }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
</tbody>
|
</div>
|
||||||
</table>
|
|
||||||
|
|
||||||
{{ $activities->links() }}
|
<div class="py-m">
|
||||||
|
{{ $activities->links() }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,9 +3,9 @@ $user - User mode to display, Can be null.
|
||||||
$user_id - Id of user to show. Must be provided.
|
$user_id - Id of user to show. Must be provided.
|
||||||
--}}
|
--}}
|
||||||
@if($user)
|
@if($user)
|
||||||
<a href="{{ $user->getEditUrl() }}" class="table-user-item">
|
<a href="{{ $user->getEditUrl() }}" class="flex-container-row inline gap-s items-center">
|
||||||
<div><img class="avatar block" src="{{ $user->getAvatar(40)}}" alt="{{ $user->name }}"></div>
|
<div class="flex-none"><img width="40" height="40" class="avatar block" src="{{ $user->getAvatar(40)}}" alt="{{ $user->name }}"></div>
|
||||||
<div>{{ $user->name }}</div>
|
<div class="flex">{{ $user->name }}</div>
|
||||||
</a>
|
</a>
|
||||||
@else
|
@else
|
||||||
[ID: {{ $user_id }}] {{ trans('common.deleted_user') }}
|
[ID: {{ $user_id }}] {{ trans('common.deleted_user') }}
|
||||||
|
|
|
@ -8,11 +8,11 @@
|
||||||
<div class="card content-wrap auto-height">
|
<div class="card content-wrap auto-height">
|
||||||
<h2 class="list-heading">{{ trans('settings.recycle_bin') }}</h2>
|
<h2 class="list-heading">{{ trans('settings.recycle_bin') }}</h2>
|
||||||
|
|
||||||
<div class="grid half left-focus">
|
<div class="flex-container-row items-center gap-x-l gap-y-m wrap">
|
||||||
<div>
|
<div class="flex-2 min-width-l">
|
||||||
<p class="text-muted">{{ trans('settings.recycle_bin_desc') }}</p>
|
<p class="text-muted mb-none">{{ trans('settings.recycle_bin_desc') }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-right">
|
<div class="flex text-m-right min-width-m">
|
||||||
<div component="dropdown" class="dropdown-container">
|
<div component="dropdown" class="dropdown-container">
|
||||||
<button refs="dropdown@toggle"
|
<button refs="dropdown@toggle"
|
||||||
type="button"
|
type="button"
|
||||||
|
@ -30,79 +30,33 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<hr class="mt-l mb-s">
|
<hr class="mt-l mb-s">
|
||||||
|
|
||||||
{!! $deletions->links() !!}
|
<div class="py-m">
|
||||||
|
{!! $deletions->links() !!}
|
||||||
|
</div>
|
||||||
|
|
||||||
<table class="table">
|
<div class="item-list">
|
||||||
<tr>
|
<div class="item-list-row flex-container-row items-center px-s bold hide-under-l">
|
||||||
<th width="30%">{{ trans('settings.recycle_bin_deleted_item') }}</th>
|
<div class="flex-2 px-m py-xs">{{ trans('settings.audit_deleted_item') }}</div>
|
||||||
<th width="20%">{{ trans('settings.recycle_bin_deleted_parent') }}</th>
|
<div class="flex-2 px-m py-xs">{{ trans('settings.recycle_bin_deleted_parent') }}</div>
|
||||||
<th width="20%">{{ trans('settings.recycle_bin_deleted_by') }}</th>
|
<div class="flex-2 px-m py-xs">{{ trans('settings.recycle_bin_deleted_by') }}</div>
|
||||||
<th width="15%">{{ trans('settings.recycle_bin_deleted_at') }}</th>
|
<div class="flex px-m py-xs">{{ trans('settings.recycle_bin_deleted_at') }}</div>
|
||||||
<th width="15%"></th>
|
<div class="flex px-m py-xs text-right"></div>
|
||||||
</tr>
|
</div>
|
||||||
@if(count($deletions) === 0)
|
@if(count($deletions) === 0)
|
||||||
<tr>
|
<div class="item-list-row px-l py-m">
|
||||||
<td colspan="5">
|
<p class="text-muted mb-none"><em>{{ trans('settings.recycle_bin_contents_empty') }}</em></p>
|
||||||
<p class="text-muted"><em>{{ trans('settings.recycle_bin_contents_empty') }}</em></p>
|
</div>
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endif
|
@endif
|
||||||
@foreach($deletions as $deletion)
|
@foreach($deletions as $deletion)
|
||||||
<tr>
|
@include('settings.recycle-bin.parts.recycle-bin-list-item', ['deletion' => $deletion])
|
||||||
<td>
|
|
||||||
<div class="table-entity-item">
|
|
||||||
<span role="presentation" class="icon text-{{$deletion->deletable->getType()}}">@icon($deletion->deletable->getType())</span>
|
|
||||||
<div class="text-{{ $deletion->deletable->getType() }}">
|
|
||||||
{{ $deletion->deletable->name }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@if($deletion->deletable instanceof \BookStack\Entities\Models\Book || $deletion->deletable instanceof \BookStack\Entities\Models\Chapter)
|
|
||||||
<div class="mb-m"></div>
|
|
||||||
@endif
|
|
||||||
@if($deletion->deletable instanceof \BookStack\Entities\Models\Book)
|
|
||||||
<div class="pl-xl block inline">
|
|
||||||
<div class="text-chapter">
|
|
||||||
@icon('chapter') {{ trans_choice('entities.x_chapters', $deletion->deletable->chapters()->withTrashed()->count()) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
@if($deletion->deletable instanceof \BookStack\Entities\Models\Book || $deletion->deletable instanceof \BookStack\Entities\Models\Chapter)
|
|
||||||
<div class="pl-xl block inline">
|
|
||||||
<div class="text-page">
|
|
||||||
@icon('page') {{ trans_choice('entities.x_pages', $deletion->deletable->pages()->withTrashed()->count()) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@if($deletion->deletable->getParent())
|
|
||||||
<div class="table-entity-item">
|
|
||||||
<span role="presentation" class="icon text-{{$deletion->deletable->getParent()->getType()}}">@icon($deletion->deletable->getParent()->getType())</span>
|
|
||||||
<div class="text-{{ $deletion->deletable->getParent()->getType() }}">
|
|
||||||
{{ $deletion->deletable->getParent()->name }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
</td>
|
|
||||||
<td>@include('settings.parts.table-user', ['user' => $deletion->deleter, 'user_id' => $deletion->deleted_by])</td>
|
|
||||||
<td width="200">{{ $deletion->created_at }}</td>
|
|
||||||
<td width="150" class="text-right">
|
|
||||||
<div component="dropdown" class="dropdown-container">
|
|
||||||
<button type="button" refs="dropdown@toggle" class="button outline">{{ trans('common.actions') }}</button>
|
|
||||||
<ul refs="dropdown@menu" class="dropdown-menu">
|
|
||||||
<li><a class="text-item" href="{{ $deletion->getUrl('/restore') }}">{{ trans('settings.recycle_bin_restore') }}</a></li>
|
|
||||||
<li><a class="text-item" href="{{ $deletion->getUrl('/destroy') }}">{{ trans('settings.recycle_bin_permanently_delete') }}</a></li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
@endforeach
|
||||||
</table>
|
</div>
|
||||||
|
|
||||||
{!! $deletions->links() !!}
|
<div class="py-m">
|
||||||
|
{!! $deletions->links() !!}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
<div class="item-list-row flex-container-row items-center px-s wrap">
|
||||||
|
<div class="flex-2 px-m py-xs min-width-xl">
|
||||||
|
<div class="flex-container-row items-center py-xs">
|
||||||
|
<span role="presentation" class="flex-none icon text-{{$deletion->deletable->getType()}}">@icon($deletion->deletable->getType())</span>
|
||||||
|
<div class="text-{{ $deletion->deletable->getType() }}">
|
||||||
|
{{ $deletion->deletable->name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@if($deletion->deletable instanceof \BookStack\Entities\Models\Book)
|
||||||
|
<div class="pl-l block inline">
|
||||||
|
<div class="text-chapter">
|
||||||
|
@icon('chapter') {{ trans_choice('entities.x_chapters', $deletion->deletable->chapters()->withTrashed()->count()) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
@if($deletion->deletable instanceof \BookStack\Entities\Models\Book || $deletion->deletable instanceof \BookStack\Entities\Models\Chapter)
|
||||||
|
<div class="pl-l block inline">
|
||||||
|
<div class="text-page">
|
||||||
|
@icon('page') {{ trans_choice('entities.x_pages', $deletion->deletable->pages()->withTrashed()->count()) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="flex-2 px-m py-xs min-width-m">
|
||||||
|
@if($deletion->deletable->getParent())
|
||||||
|
<strong class="hide-over-l">{{ trans('settings.recycle_bin_deleted_parent') }}:<br></strong>
|
||||||
|
<div class="flex-container-row items-center">
|
||||||
|
<span role="presentation" class="flex-none icon text-{{$deletion->deletable->getParent()->getType()}}">@icon($deletion->deletable->getParent()->getType())</span>
|
||||||
|
<div class="text-{{ $deletion->deletable->getParent()->getType() }}">
|
||||||
|
{{ $deletion->deletable->getParent()->name }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="flex-2 px-m py-xs flex-container-row items-center min-width-m">
|
||||||
|
<div><strong class="hide-over-l">{{ trans('settings.recycle_bin_deleted_by') }}:<br></strong>@include('settings.parts.table-user', ['user' => $deletion->deleter, 'user_id' => $deletion->deleted_by])</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex px-m py-xs min-width-s"><strong class="hide-over-l">{{ trans('settings.recycle_bin_deleted_at') }}:<br></strong>{{ $deletion->created_at }}</div>
|
||||||
|
<div class="flex px-m py-xs text-m-right min-width-s">
|
||||||
|
<div component="dropdown" class="dropdown-container">
|
||||||
|
<button type="button" refs="dropdown@toggle" class="button outline">{{ trans('common.actions') }}</button>
|
||||||
|
<ul refs="dropdown@menu" class="dropdown-menu">
|
||||||
|
<li><a class="text-item" href="{{ $deletion->getUrl('/restore') }}">{{ trans('settings.recycle_bin_restore') }}</a></li>
|
||||||
|
<li><a class="text-item" href="{{ $deletion->getUrl('/destroy') }}">{{ trans('settings.recycle_bin_permanently_delete') }}</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -12,30 +12,37 @@
|
||||||
<h1 class="list-heading">{{ trans('settings.role_user_roles') }}</h1>
|
<h1 class="list-heading">{{ trans('settings.role_user_roles') }}</h1>
|
||||||
|
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
<a href="{{ url("/settings/roles/new") }}" class="button outline">{{ trans('settings.role_create') }}</a>
|
<a href="{{ url("/settings/roles/new") }}" class="button outline my-none">{{ trans('settings.role_create') }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="table">
|
<p class="text-muted">{{ trans('settings.roles_index_desc') }}</p>
|
||||||
<tr>
|
|
||||||
<th>{{ trans('settings.role_name') }}</th>
|
|
||||||
<th></th>
|
|
||||||
<th class="text-center">{{ trans('settings.users') }}</th>
|
|
||||||
</tr>
|
|
||||||
@foreach($roles as $role)
|
|
||||||
<tr>
|
|
||||||
<td><a href="{{ url("/settings/roles/{$role->id}") }}">{{ $role->display_name }}</a></td>
|
|
||||||
<td>
|
|
||||||
@if($role->mfa_enforced)
|
|
||||||
<span title="{{ trans('settings.role_mfa_enforced') }}">@icon('lock') </span>
|
|
||||||
@endif
|
|
||||||
{{ $role->description }}
|
|
||||||
</td>
|
|
||||||
<td class="text-center">{{ $role->users->count() }}</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
|
||||||
</table>
|
|
||||||
|
|
||||||
|
<div class="flex-container-row items-center justify-space-between gap-m mt-m mb-l wrap">
|
||||||
|
<div>
|
||||||
|
<div class="block inline mr-xs">
|
||||||
|
<form method="get" action="{{ url("/settings/roles") }}">
|
||||||
|
<input type="text"
|
||||||
|
name="search"
|
||||||
|
placeholder="{{ trans('common.search') }}"
|
||||||
|
value="{{ $listOptions->getSearch() }}">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="justify-flex-end">
|
||||||
|
@include('common.sort', $listOptions->getSortControlData())
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-list">
|
||||||
|
@foreach($roles as $role)
|
||||||
|
@include('settings.roles.parts.roles-list-item', ['role' => $role])
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-m">
|
||||||
|
{{ $roles->links() }}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
<div class="item-list-row flex-container-row items-center wrap">
|
||||||
|
<div class="flex py-s px-m min-width-s">
|
||||||
|
<strong>{{ $title }}</strong> <br>
|
||||||
|
<a href="#" refs="permissions-table@toggle-row" class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
|
||||||
|
</div>
|
||||||
|
<div class="flex py-s px-m min-width-xxs">
|
||||||
|
<small class="hide-over-m bold">{{ trans('common.create') }}<br></small>
|
||||||
|
@if($permissionPrefix === 'page' || $permissionPrefix === 'chapter')
|
||||||
|
@include('settings.roles.parts.checkbox', ['permission' => $permissionPrefix . '-create-own', 'label' => trans('settings.role_own')])
|
||||||
|
<br>
|
||||||
|
@endif
|
||||||
|
@include('settings.roles.parts.checkbox', ['permission' => $permissionPrefix . '-create-all', 'label' => trans('settings.role_all')])
|
||||||
|
</div>
|
||||||
|
<div class="flex py-s px-m min-width-xxs">
|
||||||
|
<small class="hide-over-m bold">{{ trans('common.view') }}<br></small>
|
||||||
|
@include('settings.roles.parts.checkbox', ['permission' => $permissionPrefix . '-view-own', 'label' => trans('settings.role_own')])
|
||||||
|
<br>
|
||||||
|
@include('settings.roles.parts.checkbox', ['permission' => $permissionPrefix . '-view-all', 'label' => trans('settings.role_all')])
|
||||||
|
</div>
|
||||||
|
<div class="flex py-s px-m min-width-xxs">
|
||||||
|
<small class="hide-over-m bold">{{ trans('common.edit') }}<br></small>
|
||||||
|
@include('settings.roles.parts.checkbox', ['permission' => $permissionPrefix . '-update-own', 'label' => trans('settings.role_own')])
|
||||||
|
<br>
|
||||||
|
@include('settings.roles.parts.checkbox', ['permission' => $permissionPrefix . '-update-all', 'label' => trans('settings.role_all')])
|
||||||
|
</div>
|
||||||
|
<div class="flex py-s px-m min-width-xxs">
|
||||||
|
<small class="hide-over-m bold">{{ trans('common.delete') }}<br></small>
|
||||||
|
@include('settings.roles.parts.checkbox', ['permission' => $permissionPrefix . '-delete-own', 'label' => trans('settings.role_own')])
|
||||||
|
<br>
|
||||||
|
@include('settings.roles.parts.checkbox', ['permission' => $permissionPrefix . '-delete-all', 'label' => trans('settings.role_all')])
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -56,174 +56,30 @@
|
||||||
<p class="text-warn">{{ trans('settings.role_asset_admins') }}</p>
|
<p class="text-warn">{{ trans('settings.role_asset_admins') }}</p>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<table component="permissions-table" class="table toggle-switch-list compact permissions-table">
|
<div component="permissions-table"
|
||||||
<tr>
|
option:permissions-table:cell-selector=".item-list-row > div"
|
||||||
<th width="20%">
|
option:permissions-table:row-selector=".item-list-row"
|
||||||
|
class="item-list toggle-switch-list">
|
||||||
|
<div class="item-list-row flex-container-row items-center hide-under-m bold">
|
||||||
|
<div class="flex py-s px-m min-width-s">
|
||||||
<a href="#" refs="permissions-table@toggle-all" class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
|
<a href="#" refs="permissions-table@toggle-all" class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
|
||||||
</th>
|
</div>
|
||||||
<th width="20%" refs="permissions-table@toggle-column">{{ trans('common.create') }}</th>
|
<div refs="permissions-table@toggle-column" class="flex py-s px-m min-width-xxs">{{ trans('common.create') }}</div>
|
||||||
<th width="20%" refs="permissions-table@toggle-column">{{ trans('common.view') }}</th>
|
<div refs="permissions-table@toggle-column" class="flex py-s px-m min-width-xxs">{{ trans('common.view') }}</div>
|
||||||
<th width="20%" refs="permissions-table@toggle-column">{{ trans('common.edit') }}</th>
|
<div refs="permissions-table@toggle-column" class="flex py-s px-m min-width-xxs">{{ trans('common.edit') }}</div>
|
||||||
<th width="20%" refs="permissions-table@toggle-column">{{ trans('common.delete') }}</th>
|
<div refs="permissions-table@toggle-column" class="flex py-s px-m min-width-xxs">{{ trans('common.delete') }}</div>
|
||||||
</tr>
|
</div>
|
||||||
<tr>
|
@include('settings.roles.parts.asset-permissions-row', ['title' => trans('entities.shelves'), 'permissionPrefix' => 'bookshelf'])
|
||||||
<td>
|
@include('settings.roles.parts.asset-permissions-row', ['title' => trans('entities.books'), 'permissionPrefix' => 'book'])
|
||||||
<div>{{ trans('entities.shelves') }}</div>
|
@include('settings.roles.parts.asset-permissions-row', ['title' => trans('entities.chapters'), 'permissionPrefix' => 'chapter'])
|
||||||
<a href="#" refs="permissions-table@toggle-row" class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
|
@include('settings.roles.parts.asset-permissions-row', ['title' => trans('entities.pages'), 'permissionPrefix' => 'page'])
|
||||||
</td>
|
@include('settings.roles.parts.related-asset-permissions-row', ['title' => trans('entities.images'), 'permissionPrefix' => 'image', 'refMark' => '1'])
|
||||||
<td>
|
@include('settings.roles.parts.related-asset-permissions-row', ['title' => trans('entities.attachments'), 'permissionPrefix' => 'attachment'])
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'bookshelf-create-all', 'label' => trans('settings.role_all')])
|
@include('settings.roles.parts.related-asset-permissions-row', ['title' => trans('entities.comments'), 'permissionPrefix' => 'comment'])
|
||||||
</td>
|
</div>
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'bookshelf-view-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'bookshelf-view-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'bookshelf-update-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'bookshelf-update-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'bookshelf-delete-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'bookshelf-delete-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div>{{ trans('entities.books') }}</div>
|
|
||||||
<a href="#" refs="permissions-table@toggle-row" class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'book-create-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'book-view-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'book-view-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'book-update-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'book-update-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'book-delete-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'book-delete-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div>{{ trans('entities.chapters') }}</div>
|
|
||||||
<a href="#" refs="permissions-table@toggle-row" class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'chapter-create-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'chapter-create-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'chapter-view-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'chapter-view-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'chapter-update-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'chapter-update-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'chapter-delete-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'chapter-delete-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div>{{ trans('entities.pages') }}</div>
|
|
||||||
<a href="#" refs="permissions-table@toggle-row" class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'page-create-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'page-create-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'page-view-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'page-view-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'page-update-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'page-update-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'page-delete-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'page-delete-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div>{{ trans('entities.images') }}</div>
|
|
||||||
<a href="#" refs="permissions-table@toggle-row" class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
|
|
||||||
</td>
|
|
||||||
<td>@include('settings.roles.parts.checkbox', ['permission' => 'image-create-all', 'label' => ''])</td>
|
|
||||||
<td style="line-height:1.2;"><small class="faded">{{ trans('settings.role_controlled_by_asset') }}<sup>1</sup></small></td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'image-update-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'image-update-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'image-delete-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'image-delete-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div>{{ trans('entities.attachments') }}</div>
|
|
||||||
<a href="#" refs="permissions-table@toggle-row" class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
|
|
||||||
</td>
|
|
||||||
<td>@include('settings.roles.parts.checkbox', ['permission' => 'attachment-create-all', 'label' => ''])</td>
|
|
||||||
<td style="line-height:1.2;"><small class="faded">{{ trans('settings.role_controlled_by_asset') }}</small></td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'attachment-update-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'attachment-update-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'attachment-delete-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'attachment-delete-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<div>{{ trans('entities.comments') }}</div>
|
|
||||||
<a href="#" refs="permissions-table@toggle-row" class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
|
|
||||||
</td>
|
|
||||||
<td>@include('settings.roles.parts.checkbox', ['permission' => 'comment-create-all', 'label' => ''])</td>
|
|
||||||
<td style="line-height:1.2;"><small class="faded">{{ trans('settings.role_controlled_by_asset') }}</small></td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'comment-update-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'comment-update-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'comment-delete-own', 'label' => trans('settings.role_own')])
|
|
||||||
<br>
|
|
||||||
@include('settings.roles.parts.checkbox', ['permission' => 'comment-delete-all', 'label' => trans('settings.role_all')])
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</table>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<p class="text-muted text-small px-m">
|
<p class="text-muted text-small p-m">
|
||||||
<sup>1</sup> {{ trans('settings.role_asset_image_view_note') }}
|
<sup>1</sup> {{ trans('settings.role_asset_image_view_note') }}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
<div class="item-list-row flex-container-row items-center wrap">
|
||||||
|
<div class="flex py-s px-m min-width-s">
|
||||||
|
<strong>{{ $title }}</strong> <br>
|
||||||
|
<a href="#" refs="permissions-table@toggle-row" class="text-small text-primary">{{ trans('common.toggle_all') }}</a>
|
||||||
|
</div>
|
||||||
|
<div class="flex py-s px-m min-width-xxs">
|
||||||
|
<small class="hide-over-m bold">{{ trans('common.create') }}<br></small>
|
||||||
|
@include('settings.roles.parts.checkbox', ['permission' => $permissionPrefix . '-create-all', 'label' => ''])
|
||||||
|
</div>
|
||||||
|
<div class="flex py-s px-m min-width-xxs">
|
||||||
|
<small class="hide-over-m bold">{{ trans('common.view') }}<br></small>
|
||||||
|
<small class="faded">{{ trans('settings.role_controlled_by_asset') }}@if($refMark ?? false)<sup>{{ $refMark }}</sup>@endif</small>
|
||||||
|
</div>
|
||||||
|
<div class="flex py-s px-m min-width-xxs">
|
||||||
|
<small class="hide-over-m bold">{{ trans('common.edit') }}<br></small>
|
||||||
|
@include('settings.roles.parts.checkbox', ['permission' => $permissionPrefix . '-update-own', 'label' => trans('settings.role_own')])
|
||||||
|
<br>
|
||||||
|
@include('settings.roles.parts.checkbox', ['permission' => $permissionPrefix . '-update-all', 'label' => trans('settings.role_all')])
|
||||||
|
</div>
|
||||||
|
<div class="flex py-s px-m min-width-xxs">
|
||||||
|
<small class="hide-over-m bold">{{ trans('common.delete') }}<br></small>
|
||||||
|
@include('settings.roles.parts.checkbox', ['permission' => $permissionPrefix . '-delete-own', 'label' => trans('settings.role_own')])
|
||||||
|
<br>
|
||||||
|
@include('settings.roles.parts.checkbox', ['permission' => $permissionPrefix . '-delete-all', 'label' => trans('settings.role_all')])
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -0,0 +1,14 @@
|
||||||
|
<div class="item-list-row flex-container-row py-xs items-center">
|
||||||
|
<div class="py-xs px-m flex-2">
|
||||||
|
<a href="{{ url("/settings/roles/{$role->id}") }}">{{ $role->display_name }}</a><br>
|
||||||
|
@if($role->mfa_enforced)
|
||||||
|
<small title="{{ trans('settings.role_mfa_enforced') }}">@icon('lock') </small>
|
||||||
|
@endif
|
||||||
|
<small>{{ $role->description }}</small>
|
||||||
|
</div>
|
||||||
|
<div class="text-right flex py-xs px-m text-muted">
|
||||||
|
{{ trans_choice('settings.roles_x_users_assigned', $role->users_count, ['count' => $role->users_count]) }}
|
||||||
|
<br>
|
||||||
|
{{ trans_choice('settings.roles_x_permissions_provided', $role->permissions_count, ['count' => $role->permissions_count]) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -8,48 +8,48 @@
|
||||||
|
|
||||||
<div class="card content-wrap auto-height">
|
<div class="card content-wrap auto-height">
|
||||||
|
|
||||||
<div class="grid half v-center">
|
<div class="flex-container-row items-center justify-space-between wrap">
|
||||||
<h1 class="list-heading">{{ trans('settings.webhooks') }}</h1>
|
<h1 class="list-heading">{{ trans('settings.webhooks') }}</h1>
|
||||||
|
|
||||||
<div class="text-right">
|
<div>
|
||||||
<a href="{{ url("/settings/webhooks/create") }}"
|
<a href="{{ url("/settings/webhooks/create") }}"
|
||||||
class="button outline">{{ trans('settings.webhooks_create') }}</a>
|
class="button outline">{{ trans('settings.webhooks_create') }}</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if(count($webhooks) > 0)
|
<p class="text-muted">{{ trans('settings.webhooks_index_desc') }}</p>
|
||||||
|
|
||||||
<table class="table">
|
<div class="flex-container-row items-center justify-space-between gap-m mt-m mb-l wrap">
|
||||||
<tr>
|
<div>
|
||||||
<th>{{ trans('common.name') }}</th>
|
<div class="block inline mr-xs">
|
||||||
<th width="100">{{ trans('settings.webhook_events_table_header') }}</th>
|
<form method="get" action="{{ url("/settings/webhooks") }}">
|
||||||
<th width="100">{{ trans('common.status') }}</th>
|
<input type="text"
|
||||||
</tr>
|
name="search"
|
||||||
|
placeholder="{{ trans('common.search') }}"
|
||||||
|
value="{{ $listOptions->getSearch() }}">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="justify-flex-end">
|
||||||
|
@include('common.sort', $listOptions->getSortControlData())
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@if(count($webhooks) > 0)
|
||||||
|
<div class="item-list">
|
||||||
@foreach($webhooks as $webhook)
|
@foreach($webhooks as $webhook)
|
||||||
<tr>
|
@include('settings.webhooks.parts.webhooks-list-item', ['webhook' => $webhook])
|
||||||
<td>
|
|
||||||
<a href="{{ $webhook->getUrl() }}">{{ $webhook->name }}</a> <br>
|
|
||||||
<span class="small text-muted italic">{{ $webhook->endpoint }}</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@if($webhook->tracksEvent('all'))
|
|
||||||
{{ trans('settings.webhooks_events_all') }}
|
|
||||||
@else
|
|
||||||
{{ $webhook->trackedEvents->count() }}
|
|
||||||
@endif
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ trans('common.status_' . ($webhook->active ? 'active' : 'inactive')) }}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
@endforeach
|
||||||
</table>
|
</div>
|
||||||
@else
|
@else
|
||||||
<p class="text-muted empty-text px-none">
|
<p class="text-muted empty-text px-none">
|
||||||
{{ trans('settings.webhooks_none_created') }}
|
{{ trans('settings.webhooks_none_created') }}
|
||||||
</p>
|
</p>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
<div class="my-m">
|
||||||
|
{{ $webhooks->links() }}
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
<div class="item-list-row py-s">
|
||||||
|
<div class="flex-container-row">
|
||||||
|
<div class="flex-2 px-m flex-container-row items-center gap-xs">
|
||||||
|
@include('common.status-indicator', ['status' => $webhook->active])
|
||||||
|
<div> <a href="{{ $webhook->getUrl() }}">{{ $webhook->name }}</a></div>
|
||||||
|
</div>
|
||||||
|
<div class="flex px-m text-right text-muted">
|
||||||
|
@if($webhook->tracksEvent('all'))
|
||||||
|
{{ trans('settings.webhooks_events_all') }}
|
||||||
|
@else
|
||||||
|
{{ trans_choice('settings.webhooks_x_trigger_events', $webhook->tracked_events_count, ['count' => $webhook->tracked_events_count]) }}
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="px-m text-muted italic text-limit-lines-1">
|
||||||
|
<small>{{ $webhook->endpoint }}</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,7 +1,7 @@
|
||||||
@extends('layouts.tri')
|
@extends('layouts.tri')
|
||||||
|
|
||||||
@section('body')
|
@section('body')
|
||||||
@include('shelves.parts.list', ['shelves' => $shelves, 'view' => $view])
|
@include('shelves.parts.list', ['shelves' => $shelves, 'view' => $view, 'listOptions' => $listOptions])
|
||||||
@stop
|
@stop
|
||||||
|
|
||||||
@section('right')
|
@section('right')
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
|
|
||||||
<main class="content-wrap mt-m card">
|
<main class="content-wrap mt-m card">
|
||||||
|
|
||||||
<div class="grid half v-center">
|
<div class="grid half v-center">
|
||||||
<h1 class="list-heading">{{ trans('entities.shelves') }}</h1>
|
<h1 class="list-heading">{{ trans('entities.shelves') }}</h1>
|
||||||
<div class="text-right">
|
<div class="text-right">
|
||||||
@include('entities.sort', ['options' => $sortOptions, 'order' => $order, 'sort' => $sort, 'type' => 'bookshelves'])
|
@include('common.sort', $listOptions->getSortControlData())
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -31,7 +30,8 @@
|
||||||
@else
|
@else
|
||||||
<p class="text-muted">{{ trans('entities.shelves_empty') }}</p>
|
<p class="text-muted">{{ trans('entities.shelves_empty') }}</p>
|
||||||
@if(userCan('bookshelf-create-all'))
|
@if(userCan('bookshelf-create-all'))
|
||||||
<a href="{{ url("/create-shelf") }}" class="button outline">@icon('edit'){{ trans('entities.create_now') }}</a>
|
<a href="{{ url("/create-shelf") }}"
|
||||||
|
class="button outline">@icon('edit'){{ trans('entities.create_now') }}</a>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
|
|
@ -23,12 +23,7 @@
|
||||||
<h1 class="flex fit-content break-text">{{ $shelf->name }}</h1>
|
<h1 class="flex fit-content break-text">{{ $shelf->name }}</h1>
|
||||||
<div class="flex"></div>
|
<div class="flex"></div>
|
||||||
<div class="flex fit-content text-m-right my-m ml-m">
|
<div class="flex fit-content text-m-right my-m ml-m">
|
||||||
@include('entities.sort', ['options' => [
|
@include('common.sort', $listOptions->getSortControlData())
|
||||||
'default' => trans('common.sort_default'),
|
|
||||||
'name' => trans('common.sort_name'),
|
|
||||||
'created_at' => trans('common.sort_created_at'),
|
|
||||||
'updated_at' => trans('common.sort_updated_at'),
|
|
||||||
], 'order' => $order, 'sort' => $sort, 'type' => 'shelf_books'])
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -5,25 +5,28 @@
|
||||||
|
|
||||||
<main class="card content-wrap mt-xxl">
|
<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>
|
<p class="text-muted">{{ trans('entities.tags_index_desc') }}</p>
|
||||||
<div class="block inline mr-xs">
|
|
||||||
<form method="get" action="{{ url("/tags") }}">
|
<div class="flex-container-row wrap justify-space-between items-center mb-s gap-m">
|
||||||
@include('form.request-query-inputs', ['params' => ['name']])
|
<div class="block inline mr-xs">
|
||||||
<input type="text"
|
<form method="get" action="{{ url("/tags") }}">
|
||||||
name="search"
|
@include('form.request-query-inputs', ['params' => ['name']])
|
||||||
placeholder="{{ trans('common.search') }}"
|
<input type="text"
|
||||||
value="{{ $search }}">
|
name="search"
|
||||||
</form>
|
placeholder="{{ trans('common.search') }}"
|
||||||
</div>
|
value="{{ $listOptions->getSearch() }}">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div class="block inline">
|
||||||
|
@include('common.sort', $listOptions->getSortControlData())
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if($nameFilter)
|
@if($nameFilter)
|
||||||
<div class="mb-m">
|
<div class="my-m">
|
||||||
<span class="mr-xs">{{ trans('common.filter_active') }}</span>
|
<strong class="mr-xs">{{ trans('common.filter_active') }}</strong>
|
||||||
@include('entities.tag', ['tag' => new \BookStack\Actions\Tag(['name' => $nameFilter])])
|
@include('entities.tag', ['tag' => new \BookStack\Actions\Tag(['name' => $nameFilter])])
|
||||||
<form method="get" action="{{ url("/tags") }}" class="inline block">
|
<form method="get" action="{{ url("/tags") }}" class="inline block">
|
||||||
@include('form.request-query-inputs', ['params' => ['search']])
|
@include('form.request-query-inputs', ['params' => ['search']])
|
||||||
|
@ -33,13 +36,13 @@
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if(count($tags) > 0)
|
@if(count($tags) > 0)
|
||||||
<table class="table expand-to-padding mt-m">
|
<div class="item-list mt-m">
|
||||||
@foreach($tags as $tag)
|
@foreach($tags as $tag)
|
||||||
@include('tags.parts.table-row', ['tag' => $tag, 'nameFilter' => $nameFilter])
|
@include('tags.parts.tags-list-item', ['tag' => $tag, 'nameFilter' => $nameFilter])
|
||||||
@endforeach
|
@endforeach
|
||||||
</table>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div class="my-m">
|
||||||
{{ $tags->links() }}
|
{{ $tags->links() }}
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
|
|
|
@ -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>
|
|
31
resources/views/tags/parts/tags-list-item.blade.php
Normal file
31
resources/views/tags/parts/tags-list-item.blade.php
Normal 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>
|
|
@ -1,6 +1,6 @@
|
||||||
<section class="card content-wrap auto-height" id="api_tokens">
|
<section class="card content-wrap auto-height" id="api_tokens">
|
||||||
<div class="grid half mb-s">
|
<div class="flex-container-row wrap justify-space-between items-center mb-s">
|
||||||
<div><h2 class="list-heading">{{ trans('settings.users_api_tokens') }}</h2></div>
|
<h2 class="list-heading">{{ trans('settings.users_api_tokens') }}</h2>
|
||||||
<div class="text-right pt-xs">
|
<div class="text-right pt-xs">
|
||||||
@if(userCan('access-api'))
|
@if(userCan('access-api'))
|
||||||
<a href="{{ url('/api/docs') }}" class="button outline">{{ trans('settings.users_api_tokens_docs') }}</a>
|
<a href="{{ url('/api/docs') }}" class="button outline">{{ trans('settings.users_api_tokens_docs') }}</a>
|
||||||
|
@ -9,25 +9,25 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@if (count($user->apiTokens) > 0)
|
@if (count($user->apiTokens) > 0)
|
||||||
<table class="table">
|
<div class="item-list my-m">
|
||||||
<tr>
|
|
||||||
<th>{{ trans('common.name') }}</th>
|
|
||||||
<th>{{ trans('settings.users_api_tokens_expires') }}</th>
|
|
||||||
<th></th>
|
|
||||||
</tr>
|
|
||||||
@foreach($user->apiTokens as $token)
|
@foreach($user->apiTokens as $token)
|
||||||
<tr>
|
<div class="item-list-row flex-container-row items-center wrap py-xs gap-x-m">
|
||||||
<td>
|
<div class="flex px-m py-xs min-width-m">
|
||||||
{{ $token->name }} <br>
|
<a href="{{ $user->getEditUrl('/api-tokens/' . $token->id) }}">{{ $token->name }}</a> <br>
|
||||||
<span class="small text-muted italic">{{ $token->token_id }}</span>
|
<span class="small text-muted italic">{{ $token->token_id }}</span>
|
||||||
</td>
|
</div>
|
||||||
<td>{{ $token->expires_at->format('Y-m-d') ?? '' }}</td>
|
<div class="flex flex-container-row items-center min-width-m">
|
||||||
<td class="text-right">
|
<div class="flex px-m py-xs text-muted">
|
||||||
<a class="button outline small" href="{{ $user->getEditUrl('/api-tokens/' . $token->id) }}">{{ trans('common.edit') }}</a>
|
<strong class="text-small">{{ trans('settings.users_api_tokens_expires') }}</strong> <br>
|
||||||
</td>
|
{{ $token->expires_at->format('Y-m-d') ?? '' }}
|
||||||
</tr>
|
</div>
|
||||||
|
<div class="flex px-m py-xs text-right">
|
||||||
|
<a class="button outline small" href="{{ $user->getEditUrl('/api-tokens/' . $token->id) }}">{{ trans('common.edit') }}</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
</table>
|
</div>
|
||||||
@else
|
@else
|
||||||
<p class="text-muted italic py-m">{{ trans('settings.users_api_tokens_none') }}</p>
|
<p class="text-muted italic py-m">{{ trans('settings.users_api_tokens_none') }}</p>
|
||||||
@endif
|
@endif
|
||||||
|
|
|
@ -9,59 +9,34 @@
|
||||||
|
|
||||||
<div class="flex-container-row wrap justify-space-between items-center">
|
<div class="flex-container-row wrap justify-space-between items-center">
|
||||||
<h1 class="list-heading">{{ trans('settings.users') }}</h1>
|
<h1 class="list-heading">{{ trans('settings.users') }}</h1>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<div class="block inline mr-xs">
|
<a href="{{ url("/settings/users/create") }}" class="outline button my-none">{{ trans('settings.users_add_new') }}</a>
|
||||||
<form method="get" action="{{ url("/settings/users") }}">
|
|
||||||
@foreach(collect($listDetails)->except('search') as $name => $val)
|
|
||||||
<input type="hidden" name="{{ $name }}" value="{{ $val }}">
|
|
||||||
@endforeach
|
|
||||||
<input type="text" name="search" placeholder="{{ trans('settings.users_search') }}" @if($listDetails['search']) value="{{$listDetails['search']}}" @endif>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<a href="{{ url("/settings/users/create") }}" class="outline button mt-none">{{ trans('settings.users_add_new') }}</a>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<table class="table">
|
<p class="text-muted">{{ trans('settings.users_index_desc') }}</p>
|
||||||
<tr>
|
|
||||||
<th width="9%"></th>
|
<div class="flex-container-row items-center justify-space-between gap-m mt-m mb-l wrap">
|
||||||
<th width="36%">
|
<div>
|
||||||
<a href="{{ sortUrl('/settings/users', $listDetails, ['sort' => 'name']) }}">{{ trans('auth.name') }}</a>
|
<div class="block inline mr-xs">
|
||||||
/
|
<form method="get" action="{{ url("/settings/users") }}">
|
||||||
<a href="{{ sortUrl('/settings/users', $listDetails, ['sort' => 'email']) }}">{{ trans('auth.email') }}</a>
|
<input type="text"
|
||||||
</th>
|
name="search"
|
||||||
<th width="35%">{{ trans('settings.role_user_roles') }}</th>
|
placeholder="{{ trans('settings.users_search') }}"
|
||||||
<th class="text-right" width="20%">
|
value="{{ $listOptions->getSearch() }}">
|
||||||
<a href="{{ sortUrl('/settings/users', $listDetails, ['sort' => 'last_activity_at']) }}">{{ trans('settings.users_latest_activity') }}</a>
|
</form>
|
||||||
</th>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
|
<div class="justify-flex-end">
|
||||||
|
@include('common.sort', $listOptions->getSortControlData())
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="item-list">
|
||||||
@foreach($users as $user)
|
@foreach($users as $user)
|
||||||
<tr>
|
@include('users.parts.users-list-item', ['user' => $user])
|
||||||
<td class="text-center" style="line-height: 0;"><img class="avatar med" src="{{ $user->getAvatar(40)}}" alt="{{ $user->name }}"></td>
|
|
||||||
<td>
|
|
||||||
<a href="{{ url("/settings/users/{$user->id}") }}">
|
|
||||||
{{ $user->name }}
|
|
||||||
<br>
|
|
||||||
<span class="text-muted">{{ $user->email }}</span>
|
|
||||||
@if($user->mfa_values_count > 0)
|
|
||||||
<span title="MFA Configured" class="text-pos">@icon('lock')</span>
|
|
||||||
@endif
|
|
||||||
</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
@foreach($user->roles as $index => $role)
|
|
||||||
<small><a href="{{ url("/settings/roles/{$role->id}") }}">{{$role->display_name}}</a>@if($index !== count($user->roles) -1),@endif</small>
|
|
||||||
@endforeach
|
|
||||||
</td>
|
|
||||||
<td class="text-right text-muted">
|
|
||||||
@if($user->last_activity_at)
|
|
||||||
<small title="{{ $user->last_activity_at->format('Y-m-d H:i:s') }}">{{ $user->last_activity_at->diffForHumans() }}</small>
|
|
||||||
@endif
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
@endforeach
|
@endforeach
|
||||||
</table>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
{{ $users->links() }}
|
{{ $users->links() }}
|
||||||
|
|
27
resources/views/users/parts/users-list-item.blade.php
Normal file
27
resources/views/users/parts/users-list-item.blade.php
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
<div class="flex-container-row item-list-row items-center wrap py-xs">
|
||||||
|
<div class="px-m py-xs flex-container-row items-center flex-2 gap-l min-width-m">
|
||||||
|
<img class="avatar med" width="40" height="40" src="{{ $user->getAvatar(40)}}" alt="{{ $user->name }}">
|
||||||
|
<a href="{{ url("/settings/users/{$user->id}") }}">
|
||||||
|
{{ $user->name }}
|
||||||
|
<br>
|
||||||
|
<span class="text-muted">{{ $user->email }}</span>
|
||||||
|
@if($user->mfa_values_count > 0)
|
||||||
|
<span title="MFA Configured" class="text-pos">@icon('lock')</span>
|
||||||
|
@endif
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="flex-container-row items-center flex-3 min-width-m">
|
||||||
|
<div class="px-m py-xs flex">
|
||||||
|
@foreach($user->roles as $index => $role)
|
||||||
|
<small><a href="{{ url("/settings/roles/{$role->id}") }}">{{$role->display_name}}</a>@if($index !== count($user->roles) -1),@endif</small>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
<div class="px-m py-xs flex text-right text-muted">
|
||||||
|
@if($user->last_activity_at)
|
||||||
|
<small>{{ trans('settings.users_latest_activity') }}</small>
|
||||||
|
<br>
|
||||||
|
<small title="{{ $user->last_activity_at->format('Y-m-d H:i:s') }}">{{ $user->last_activity_at->diffForHumans() }}</small>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -29,6 +29,7 @@ use BookStack\Http\Controllers\StatusController;
|
||||||
use BookStack\Http\Controllers\TagController;
|
use BookStack\Http\Controllers\TagController;
|
||||||
use BookStack\Http\Controllers\UserApiTokenController;
|
use BookStack\Http\Controllers\UserApiTokenController;
|
||||||
use BookStack\Http\Controllers\UserController;
|
use BookStack\Http\Controllers\UserController;
|
||||||
|
use BookStack\Http\Controllers\UserPreferencesController;
|
||||||
use BookStack\Http\Controllers\UserProfileController;
|
use BookStack\Http\Controllers\UserProfileController;
|
||||||
use BookStack\Http\Controllers\UserSearchController;
|
use BookStack\Http\Controllers\UserSearchController;
|
||||||
use BookStack\Http\Controllers\WebhookController;
|
use BookStack\Http\Controllers\WebhookController;
|
||||||
|
@ -239,18 +240,20 @@ Route::middleware('auth')->group(function () {
|
||||||
Route::get('/settings/users', [UserController::class, 'index']);
|
Route::get('/settings/users', [UserController::class, 'index']);
|
||||||
Route::get('/settings/users/create', [UserController::class, 'create']);
|
Route::get('/settings/users/create', [UserController::class, 'create']);
|
||||||
Route::get('/settings/users/{id}/delete', [UserController::class, 'delete']);
|
Route::get('/settings/users/{id}/delete', [UserController::class, 'delete']);
|
||||||
Route::patch('/settings/users/{id}/switch-books-view', [UserController::class, 'switchBooksView']);
|
|
||||||
Route::patch('/settings/users/{id}/switch-shelves-view', [UserController::class, 'switchShelvesView']);
|
|
||||||
Route::patch('/settings/users/{id}/switch-shelf-view', [UserController::class, 'switchShelfView']);
|
|
||||||
Route::patch('/settings/users/{id}/change-sort/{type}', [UserController::class, 'changeSort']);
|
|
||||||
Route::patch('/settings/users/{id}/update-expansion-preference/{key}', [UserController::class, 'updateExpansionPreference']);
|
|
||||||
Route::patch('/settings/users/toggle-dark-mode', [UserController::class, 'toggleDarkMode']);
|
|
||||||
Route::patch('/settings/users/update-code-language-favourite', [UserController::class, 'updateCodeLanguageFavourite']);
|
|
||||||
Route::post('/settings/users/create', [UserController::class, 'store']);
|
Route::post('/settings/users/create', [UserController::class, 'store']);
|
||||||
Route::get('/settings/users/{id}', [UserController::class, 'edit']);
|
Route::get('/settings/users/{id}', [UserController::class, 'edit']);
|
||||||
Route::put('/settings/users/{id}', [UserController::class, 'update']);
|
Route::put('/settings/users/{id}', [UserController::class, 'update']);
|
||||||
Route::delete('/settings/users/{id}', [UserController::class, 'destroy']);
|
Route::delete('/settings/users/{id}', [UserController::class, 'destroy']);
|
||||||
|
|
||||||
|
// User Preferences
|
||||||
|
Route::patch('/settings/users/{id}/switch-books-view', [UserPreferencesController::class, 'switchBooksView']);
|
||||||
|
Route::patch('/settings/users/{id}/switch-shelves-view', [UserPreferencesController::class, 'switchShelvesView']);
|
||||||
|
Route::patch('/settings/users/{id}/switch-shelf-view', [UserPreferencesController::class, 'switchShelfView']);
|
||||||
|
Route::patch('/settings/users/{id}/change-sort/{type}', [UserPreferencesController::class, 'changeSort']);
|
||||||
|
Route::patch('/settings/users/{id}/update-expansion-preference/{key}', [UserPreferencesController::class, 'updateExpansionPreference']);
|
||||||
|
Route::patch('/settings/users/toggle-dark-mode', [UserPreferencesController::class, 'toggleDarkMode']);
|
||||||
|
Route::patch('/settings/users/update-code-language-favourite', [UserPreferencesController::class, 'updateCodeLanguageFavourite']);
|
||||||
|
|
||||||
// User API Tokens
|
// User API Tokens
|
||||||
Route::get('/settings/users/{userId}/create-api-token', [UserApiTokenController::class, 'create']);
|
Route::get('/settings/users/{userId}/create-api-token', [UserApiTokenController::class, 'create']);
|
||||||
Route::post('/settings/users/{userId}/create-api-token', [UserApiTokenController::class, 'store']);
|
Route::post('/settings/users/{userId}/create-api-token', [UserApiTokenController::class, 'store']);
|
||||||
|
|
|
@ -51,7 +51,7 @@ class AuditLogTest extends TestCase
|
||||||
$resp->assertSeeText($page->name);
|
$resp->assertSeeText($page->name);
|
||||||
$resp->assertSeeText('page_create');
|
$resp->assertSeeText('page_create');
|
||||||
$resp->assertSeeText($activity->created_at->toDateTimeString());
|
$resp->assertSeeText($activity->created_at->toDateTimeString());
|
||||||
$this->withHtml($resp)->assertElementContains('.table-user-item', $admin->name);
|
$this->withHtml($resp)->assertElementContains('a[href*="users/' . $admin->id . '"]', $admin->name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_shows_name_for_deleted_items()
|
public function test_shows_name_for_deleted_items()
|
||||||
|
|
|
@ -195,12 +195,12 @@ class PageRevisionTest extends TestCase
|
||||||
$this->createRevisions($page, 1, ['html' => 'new page html']);
|
$this->createRevisions($page, 1, ['html' => 'new page html']);
|
||||||
|
|
||||||
$resp = $this->asAdmin()->get($page->refresh()->getUrl('/revisions'));
|
$resp = $this->asAdmin()->get($page->refresh()->getUrl('/revisions'));
|
||||||
$this->withHtml($resp)->assertElementContains('td', '(WYSIWYG)');
|
$this->withHtml($resp)->assertElementContains('.item-list-row > div:nth-child(2)', 'WYSIWYG)');
|
||||||
$this->withHtml($resp)->assertElementNotContains('td', '(Markdown)');
|
$this->withHtml($resp)->assertElementNotContains('.item-list-row > div:nth-child(2)', 'Markdown)');
|
||||||
|
|
||||||
$this->createRevisions($page, 1, ['markdown' => '# Some markdown content']);
|
$this->createRevisions($page, 1, ['markdown' => '# Some markdown content']);
|
||||||
$resp = $this->get($page->refresh()->getUrl('/revisions'));
|
$resp = $this->get($page->refresh()->getUrl('/revisions'));
|
||||||
$this->withHtml($resp)->assertElementContains('td', '(Markdown)');
|
$this->withHtml($resp)->assertElementContains('.item-list-row > div:nth-child(2)', 'Markdown)');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_revision_restore_action_only_visible_with_permission()
|
public function test_revision_restore_action_only_visible_with_permission()
|
||||||
|
|
|
@ -164,7 +164,7 @@ class TagTest extends TestCase
|
||||||
$resp->assertSee('OtherTestContent');
|
$resp->assertSee('OtherTestContent');
|
||||||
$resp->assertDontSee('OtherTagName');
|
$resp->assertDontSee('OtherTagName');
|
||||||
$resp->assertSee('Active Filter:');
|
$resp->assertSee('Active Filter:');
|
||||||
$this->withHtml($resp)->assertElementCount('table .tag-item', 2);
|
$this->withHtml($resp)->assertElementCount('.item-list .tag-item', 2);
|
||||||
$this->withHtml($resp)->assertElementContains('form[action$="/tags"]', 'Clear Filter');
|
$this->withHtml($resp)->assertElementContains('form[action$="/tags"]', 'Clear Filter');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,11 +62,11 @@ class RecycleBinTest extends TestCase
|
||||||
|
|
||||||
$viewReq = $this->asAdmin()->get('/settings/recycle-bin');
|
$viewReq = $this->asAdmin()->get('/settings/recycle-bin');
|
||||||
$html = $this->withHtml($viewReq);
|
$html = $this->withHtml($viewReq);
|
||||||
$html->assertElementContains('table.table', $page->name);
|
$html->assertElementContains('.item-list-row', $page->name);
|
||||||
$html->assertElementContains('table.table', $editor->name);
|
$html->assertElementContains('.item-list-row', $editor->name);
|
||||||
$html->assertElementContains('table.table', $book->name);
|
$html->assertElementContains('.item-list-row', $book->name);
|
||||||
$html->assertElementContains('table.table', $book->pages_count . ' Pages');
|
$html->assertElementContains('.item-list-row', $book->pages_count . ' Pages');
|
||||||
$html->assertElementContains('table.table', $book->chapters_count . ' Chapters');
|
$html->assertElementContains('.item-list-row', $book->chapters_count . ' Chapters');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_recycle_bin_empty()
|
public function test_recycle_bin_empty()
|
||||||
|
|
|
@ -29,21 +29,6 @@ class UserPreferencesTest extends TestCase
|
||||||
$this->assertEquals('desc', setting()->getForCurrentUser('books_sort_order'));
|
$this->assertEquals('desc', setting()->getForCurrentUser('books_sort_order'));
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_update_sort_preference_defaults()
|
|
||||||
{
|
|
||||||
$editor = $this->getEditor();
|
|
||||||
$this->actingAs($editor);
|
|
||||||
|
|
||||||
$updateRequest = $this->patch('/settings/users/' . $editor->id . '/change-sort/bookshelves', [
|
|
||||||
'sort' => 'cat',
|
|
||||||
'order' => 'dog',
|
|
||||||
]);
|
|
||||||
$updateRequest->assertStatus(302);
|
|
||||||
|
|
||||||
$this->assertEquals('name', setting()->getForCurrentUser('bookshelves_sort'));
|
|
||||||
$this->assertEquals('asc', setting()->getForCurrentUser('bookshelves_sort_order'));
|
|
||||||
}
|
|
||||||
|
|
||||||
public function test_update_sort_bad_entity_type_handled()
|
public function test_update_sort_bad_entity_type_handled()
|
||||||
{
|
{
|
||||||
$editor = $this->getEditor();
|
$editor = $this->getEditor();
|
||||||
|
|
Loading…
Reference in New Issue
Block a user