const IMPORT_RUN = 'import_run';
const IMPORT_DELETE = 'import_delete';
+ const SORT_SET_CREATE = 'sort_set_create';
+ const SORT_SET_UPDATE = 'sort_set_update';
+ const SORT_SET_DELETE = 'sort_set_delete';
+
/**
* Get all the possible values.
*/
namespace BookStack\Sorting;
+use BookStack\Activity\Models\Loggable;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
* @property Carbon $created_at
* @property Carbon $updated_at
*/
-class SortSet extends Model
+class SortSet extends Model implements Loggable
{
/**
* @return SortSetOperation[]
*/
public function getOperations(): array
{
- $strOptions = explode(',', $this->sequence);
- $options = array_map(fn ($val) => SortSetOperation::tryFrom($val), $strOptions);
- return array_filter($options);
+ return SortSetOperation::fromSequence($this->sequence);
}
/**
$values = array_map(fn (SortSetOperation $opt) => $opt->value, $options);
$this->sequence = implode(',', $values);
}
+
+ public function logDescriptor(): string
+ {
+ return "({$this->id}) {$this->name}";
+ }
+
+ public function getUrl(): string
+ {
+ return url("/settings/sorting/sets/{$this->id}");
+ }
}
namespace BookStack\Sorting;
+use BookStack\Activity\ActivityType;
use BookStack\Http\Controller;
+use BookStack\Http\Request;
class SortSetController extends Controller
{
public function create()
{
+ $this->setPageTitle(trans('settings.sort_set_create'));
+
return view('settings.sort-sets.create');
}
+
+ public function store(Request $request)
+ {
+ $this->validate($request, [
+ 'name' => ['required', 'string', 'min:1', 'max:200'],
+ 'sequence' => ['required', 'string', 'min:1'],
+ ]);
+
+ $operations = SortSetOperation::fromSequence($request->input('sequence'));
+ if (count($operations) === 0) {
+ return redirect()->withInput()->withErrors(['sequence' => 'No operations set.']);
+ }
+
+ $set = new SortSet();
+ $set->name = $request->input('name');
+ $set->setOperations($operations);
+ $set->save();
+
+ $this->logActivity(ActivityType::SORT_SET_CREATE, $set);
+
+ return redirect('/settings/sorting');
+ }
+
+ public function edit(string $id)
+ {
+ $set = SortSet::query()->findOrFail($id);
+
+ $this->setPageTitle(trans('settings.sort_set_edit'));
+
+ return view('settings.sort-sets.edit', ['set' => $set]);
+ }
+
+ public function update(string $id, Request $request)
+ {
+ $this->validate($request, [
+ 'name' => ['required', 'string', 'min:1', 'max:200'],
+ 'sequence' => ['required', 'string', 'min:1'],
+ ]);
+
+ $set = SortSet::query()->findOrFail($id);
+ $operations = SortSetOperation::fromSequence($request->input('sequence'));
+ if (count($operations) === 0) {
+ return redirect()->withInput()->withErrors(['sequence' => 'No operations set.']);
+ }
+
+ $set->name = $request->input('name');
+ $set->setOperations($operations);
+ $set->save();
+
+ $this->logActivity(ActivityType::SORT_SET_UPDATE, $set);
+
+ return redirect('/settings/sorting');
+ }
+
+ public function destroy(string $id)
+ {
+ $set = SortSet::query()->findOrFail($id);
+
+ // TODO - Check if it's in use
+
+ $set->delete();
+ $this->logActivity(ActivityType::SORT_SET_DELETE, $set);
+
+ return redirect('/settings/sorting');
+ }
}
public static function allExcluding(array $operations): array
{
$all = SortSetOperation::cases();
- return array_diff($all, $operations);
+ $filtered = array_filter($all, function (SortSetOperation $operation) use ($operations) {
+ return !in_array($operation, $operations);
+ });
+ return array_values($filtered);
+ }
+
+ /**
+ * Create a set of operations from a string sequence representation.
+ * (values seperated by commas).
+ * @return SortSetOperation[]
+ */
+ public static function fromSequence(string $sequence): array
+ {
+ $strOptions = explode(',', $sequence);
+ $options = array_map(fn ($val) => SortSetOperation::tryFrom($val), $strOptions);
+ return array_filter($options);
}
}
'comment_update' => 'updated comment',
'comment_delete' => 'deleted comment',
+ // Sort Sets
+ 'sort_set_create' => 'created sort set',
+ 'sort_set_create_notification' => 'Sort set successfully created',
+ 'sort_set_update' => 'updated sort set',
+ 'sort_set_update_notification' => 'Sort set successfully update',
+ 'sort_set_delete' => 'deleted sort set',
+ 'sort_set_delete_notification' => 'Sort set successfully deleted',
+
// Other
'permissions_update' => 'updated permissions',
];
'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_delete' => 'Delete Sort Set',
+ 'sort_set_delete_desc' => 'Remove this sort set from the system. Deletion will only go ahead if the sort is not in active use.',
'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',
</div>
</div>
-{{-- TODO--}}
+ @php
+ $sortSets = \BookStack\Sorting\SortSet::query()->orderBy('name', 'asc')->get();
+ @endphp
+ @if(empty($sortSets))
+ <p class="italic text-muted">{{ trans('common.no_items') }}</p>
+ @else
+ <div class="item-list">
+ @foreach($sortSets as $set)
+ @include('settings.sort-sets.parts.sort-set-list-item', ['set' => $set])
+ @endforeach
+ </div>
+ @endif
</div>
@endsection
\ No newline at end of file
--- /dev/null
+@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_edit') }}</h1>
+
+ <form action="{{ $set->getUrl() }}" method="POST">
+ {{ method_field('PUT') }}
+ {{ csrf_field() }}
+
+ @include('settings.sort-sets.parts.form', ['model' => $set])
+
+ <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 class="card content-wrap auto-height">
+ <div class="flex-container-row items-center gap-l">
+ <div>
+ <h2 class="list-heading">{{ trans('settings.sort_set_delete') }}</h2>
+ <p class="text-muted">{{ trans('settings.sort_set_delete_desc') }}</p>
+ </div>
+ <div class="flex">
+ <form action="{{ $set->getUrl() }}" method="POST">
+ {{ method_field('DELETE') }}
+ {{ csrf_field() }}
+ <div class="text-right">
+ <button type="submit" class="button outline">{{ trans('common.delete') }}</button>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
+
+@stop
<div component="sort-set-manager">
<label class="setting-list-label">{{ trans('settings.sort_set_operations') }}</label>
<p class="text-muted text-small">{{ trans('settings.sort_set_operations_desc') }}</p>
+ @include('form.errors', ['name' => 'sequence'])
- <input refs="sort-set-manager@input" type="hidden" name="books"
- value="{{ $model?->sequence ?? '' }}">
+ <input refs="sort-set-manager@input" type="hidden" name="sequence"
+ value="{{ old('sequence') ?? $model?->sequence ?? '' }}">
+
+ @php
+ $configuredOps = old('sequence') ? \BookStack\Sorting\SortSetOperation::fromSequence(old('sequence')) : ($model?->getOperations() ?? []);
+ @endphp
<div class="grid half">
<div class="form-group">
aria-labelledby="sort-set-configured-operations"
class="scroll-box configured-option-list">
<li class="text-muted empty-state px-m py-s italic text-small">{{ trans('settings.sort_set_configured_operations_empty') }}</li>
- @foreach(($model?->getOperations() ?? []) as $option)
- @include('settings.sort-sets.parts.operation')
+
+ @foreach($configuredOps as $operation)
+ @include('settings.sort-sets.parts.operation', ['operation' => $operation])
@endforeach
</ul>
</div>
aria-labelledby="sort-set-available-operations"
class="scroll-box available-option-list">
<li class="text-muted empty-state px-m py-s italic text-small">{{ trans('settings.sort_set_available_operations_empty') }}</li>
- @foreach(\BookStack\Sorting\SortSetOperation::allExcluding($model?->getOperations() ?? []) as $operation)
+ @foreach(\BookStack\Sorting\SortSetOperation::allExcluding($configuredOps) as $operation)
@include('settings.sort-sets.parts.operation', ['operation' => $operation])
@endforeach
</ul>
--- /dev/null
+<div class="item-list-row flex-container-row py-xs items-center">
+ <div class="py-xs px-m flex-2">
+ <a href="{{ $set->getUrl() }}">{{ $set->name }}</a>
+ </div>
+ <div class="px-m text-small text-muted">
+ {{ implode(', ', array_map(fn ($op) => $op->getLabel(), $set->getOperations())) }}
+ </div>
+</div>
\ No newline at end of file