Sorting: Started sort set routes and form

This commit is contained in:
Dan Brown 2025-02-03 16:48:57 +00:00
parent a34023f715
commit bf8a84a8b1
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
7 changed files with 182 additions and 6 deletions

View File

@ -0,0 +1,19 @@
<?php
namespace BookStack\Sorting;
use BookStack\Http\Controller;
class SortSetController extends Controller
{
public function __construct()
{
$this->middleware('can:settings-manage');
// TODO - Test
}
public function create()
{
return view('settings.sort-sets.create');
}
}

View File

@ -13,4 +13,32 @@ enum SortSetOption: string
case UpdateDateDesc = 'updated_date_desc';
case ChaptersFirst = 'chapters_first';
case ChaptersLast = 'chapters_last';
/**
* Provide a translated label string for this option.
*/
public function getLabel(): string
{
$key = $this->value;
$label = '';
if (str_ends_with($key, '_asc')) {
$key = substr($key, 0, -4);
$label = trans('settings.sort_set_op_asc');
} elseif (str_ends_with($key, '_desc')) {
$key = substr($key, 0, -5);
$label = trans('settings.sort_set_op_desc');
}
$label = trans('settings.sort_set_op_' . $key) . ' ' . $label;
return trim($label);
}
/**
* @return SortSetOption[]
*/
public static function allExcluding(array $options): array
{
$all = SortSetOption::cases();
return array_diff($all, $options);
}
}

View File

@ -80,6 +80,22 @@ return [
'sorting_book_default_desc' => 'Select the default sort set to apply to new books. This won\'t affect existing books, and can be overridden per-book.',
'sorting_sets' => 'Sort Sets',
'sorting_sets_desc' => 'These are predefined sorting operations which can be applied to content in the system.',
'sort_set_create' => 'Create Sort Set',
'sort_set_edit' => 'Edit Sort Set',
'sort_set_details' => 'Sort Set Details',
'sort_set_details_desc' => 'Set a name for this sort set, which will appear in lists when users are selecting a sort.',
'sort_set_operations' => 'Sort Operations',
'sort_set_operations_desc' => 'Configure the sort actions to be performed in this set by moving them from the list of available operations. Upon use, the operations will be applied in order, from top to bottom.',
'sort_set_available_operations' => 'Available Operations',
'sort_set_configured_operations' => 'Configured Operations',
'sort_set_op_asc' => '(Asc)',
'sort_set_op_desc' => '(Desc)',
'sort_set_op_name' => 'Name - Alphabetical',
'sort_set_op_name_numeric' => 'Name - Numeric',
'sort_set_op_created_date' => 'Created Date',
'sort_set_op_updated_date' => 'Updated Date',
'sort_set_op_chapters_first' => 'Chapters First',
'sort_set_op_chapters_last' => 'Chapters Last',
// Maintenance settings
'maint' => 'Maintenance',

View File

@ -42,8 +42,16 @@
@section('after-card')
<div class="card content-wrap auto-height">
<h2 class="list-heading">{{ trans('settings.sorting_sets') }}</h2>
<p class="text-muted">{{ trans('settings.sorting_sets_desc') }}</p>
<div class="flex-container-row items-center gap-m">
<div class="flex">
<h2 class="list-heading">{{ trans('settings.sorting_sets') }}</h2>
<p class="text-muted">{{ trans('settings.sorting_sets_desc') }}</p>
</div>
<div>
<a href="{{ url('/settings/sorting/sets/new') }}" class="button outline">{{ trans('settings.sort_set_create') }}</a>
</div>
</div>
{{-- TODO--}}
</div>
@endsection

View File

@ -0,0 +1,24 @@
@extends('layouts.simple')
@section('body')
<div class="container small">
@include('settings.parts.navbar', ['selected' => 'settings'])
<div class="card content-wrap auto-height">
<h1 class="list-heading">{{ trans('settings.sort_set_create') }}</h1>
<form action="{{ url("/settings/sorting/sets") }}" method="POST">
{{ csrf_field() }}
@include('settings.sort-sets.parts.form', ['model' => null])
<div class="form-group text-right">
<a href="{{ url("/settings/sorting") }}" class="button outline">{{ trans('common.cancel') }}</a>
<button type="submit" class="button">{{ trans('common.save') }}</button>
</div>
</form>
</div>
</div>
@stop

View File

@ -0,0 +1,74 @@
<div class="setting-list">
<div class="grid half">
<div>
<label class="setting-list-label">{{ trans('settings.sort_set_details') }}</label>
<p class="text-muted text-small">{{ trans('settings.sort_set_details_desc') }}</p>
</div>
<div>
<div class="form-group">
<label for="name">{{ trans('common.name') }}</label>
@include('form.text', ['name' => 'name'])
</div>
</div>
</div>
<div>
<label class="setting-list-label">{{ trans('settings.sort_set_operations') }}</label>
<p class="text-muted text-small">{{ trans('settings.sort_set_operations_desc') }}</p>
<div class="grid half">
<div class="form-group">
<label for="books" id="sort-set-configured-operations">{{ trans('settings.sort_set_configured_operations') }}</label>
<ul refs="sort-set@configured-operations-list"
aria-labelledby="sort-set-configured-operations"
class="scroll-box">
@foreach(($model?->getOptions() ?? []) as $option)
<li data-id="{{ $option->value }}"
class="scroll-box-item">
<div class="handle px-s">@icon('grip')</div>
<div>{{ $option->getLabel() }}</div>
<div class="buttons flex-container-row items-center ml-auto px-xxs py-xs">
<button type="button" data-action="move_up" class="icon-button p-xxs"
title="{{ trans('entities.books_sort_move_up') }}">@icon('chevron-up')</button>
<button type="button" data-action="move_down" class="icon-button p-xxs"
title="{{ trans('entities.books_sort_move_down') }}">@icon('chevron-down')</button>
<button type="button" data-action="remove" class="icon-button p-xxs"
title="{{ trans('common.remove') }}">@icon('remove')</button>
<button type="button" data-action="add" class="icon-button p-xxs"
title="{{ trans('common.add') }}">@icon('add-small')</button>
</div>
</li>
@endforeach
</ul>
</div>
<div class="form-group">
<label for="books" id="sort-set-available-operations">{{ trans('settings.sort_set_available_operations') }}</label>
<ul refs="sort-set@available-operations-list"
aria-labelledby="sort-set-available-operations"
class="scroll-box">
@foreach(\BookStack\Sorting\SortSetOption::allExcluding($model?->getOptions() ?? []) as $option)
<li data-id="{{ $option->value }}"
class="scroll-box-item">
<div class="handle px-s">@icon('grip')</div>
<div>{{ $option->getLabel() }}</div>
<div class="buttons flex-container-row items-center ml-auto px-xxs py-xs">
<button type="button" data-action="move_up" class="icon-button p-xxs"
title="{{ trans('entities.books_sort_move_up') }}">@icon('chevron-up')</button>
<button type="button" data-action="move_down" class="icon-button p-xxs"
title="{{ trans('entities.books_sort_move_down') }}">@icon('chevron-down')</button>
<button type="button" data-action="remove" class="icon-button p-xxs"
title="{{ trans('common.remove') }}">@icon('remove')</button>
<button type="button" data-action="add" class="icon-button p-xxs"
title="{{ trans('common.add') }}">@icon('add-small')</button>
</div>
</li>
@endforeach
</ul>
</div>
</div>
</div>
</div>

View File

@ -13,7 +13,7 @@ use BookStack\Permissions\PermissionsController;
use BookStack\References\ReferenceController;
use BookStack\Search\SearchController;
use BookStack\Settings as SettingControllers;
use BookStack\Sorting\BookSortController;
use BookStack\Sorting as SortingControllers;
use BookStack\Theming\ThemeController;
use BookStack\Uploads\Controllers as UploadControllers;
use BookStack\Users\Controllers as UserControllers;
@ -67,7 +67,7 @@ Route::middleware('auth')->group(function () {
Route::get('/books/{slug}/edit', [EntityControllers\BookController::class, 'edit']);
Route::put('/books/{slug}', [EntityControllers\BookController::class, 'update']);
Route::delete('/books/{id}', [EntityControllers\BookController::class, 'destroy']);
Route::get('/books/{slug}/sort-item', [BookSortController::class, 'showItem']);
Route::get('/books/{slug}/sort-item', [SortingControllers\BookSortController::class, 'showItem']);
Route::get('/books/{slug}', [EntityControllers\BookController::class, 'show']);
Route::get('/books/{bookSlug}/permissions', [PermissionsController::class, 'showForBook']);
Route::put('/books/{bookSlug}/permissions', [PermissionsController::class, 'updateForBook']);
@ -75,8 +75,8 @@ Route::middleware('auth')->group(function () {
Route::get('/books/{bookSlug}/copy', [EntityControllers\BookController::class, 'showCopy']);
Route::post('/books/{bookSlug}/copy', [EntityControllers\BookController::class, 'copy']);
Route::post('/books/{bookSlug}/convert-to-shelf', [EntityControllers\BookController::class, 'convertToShelf']);
Route::get('/books/{bookSlug}/sort', [BookSortController::class, 'show']);
Route::put('/books/{bookSlug}/sort', [BookSortController::class, 'update']);
Route::get('/books/{bookSlug}/sort', [SortingControllers\BookSortController::class, 'show']);
Route::put('/books/{bookSlug}/sort', [SortingControllers\BookSortController::class, 'update']);
Route::get('/books/{slug}/references', [ReferenceController::class, 'book']);
Route::get('/books/{bookSlug}/export/html', [ExportControllers\BookExportController::class, 'html']);
Route::get('/books/{bookSlug}/export/pdf', [ExportControllers\BookExportController::class, 'pdf']);
@ -295,6 +295,13 @@ Route::middleware('auth')->group(function () {
Route::get('/settings/webhooks/{id}/delete', [ActivityControllers\WebhookController::class, 'delete']);
Route::delete('/settings/webhooks/{id}', [ActivityControllers\WebhookController::class, 'destroy']);
// Sort Sets
Route::get('/settings/sorting/sets/new', [SortingControllers\SortSetController::class, 'create']);
Route::post('/settings/sorting/sets', [SortingControllers\SortSetController::class, 'store']);
Route::get('/settings/sorting/sets/{id}', [SortingControllers\SortSetController::class, 'edit']);
Route::put('/settings/sorting/sets/{id}', [SortingControllers\SortSetController::class, 'update']);
Route::delete('/settings/sorting/sets/{id}', [SortingControllers\SortSetController::class, 'destroy']);
// Settings
Route::get('/settings', [SettingControllers\SettingController::class, 'index'])->name('settings');
Route::get('/settings/{category}', [SettingControllers\SettingController::class, 'category'])->name('settings.category');