Renamed based on feedback from Tim and Script on Discord.
Also fixed flaky test
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';
+ const SORT_RULE_CREATE = 'sort_rule_create';
+ const SORT_RULE_UPDATE = 'sort_rule_update';
+ const SORT_RULE_DELETE = 'sort_rule_delete';
/**
* Get all the possible values.
use BookStack\Entities\Models\Book;
use BookStack\Sorting\BookSorter;
-use BookStack\Sorting\SortSet;
+use BookStack\Sorting\SortRule;
use Illuminate\Console\Command;
class AssignSortSetCommand extends Command
return $this->listSortSets();
}
- $set = SortSet::query()->find($sortSetId);
+ $set = SortRule::query()->find($sortSetId);
if ($this->option('all-books')) {
$query = Book::query();
} else if ($this->option('books-without-sort')) {
protected function listSortSets(): int
{
- $sets = SortSet::query()->orderBy('id', 'asc')->get();
+ $sets = SortRule::query()->orderBy('id', 'asc')->get();
$this->error("Sort set ID required!");
$this->warn("\nAvailable sort sets:");
foreach ($sets as $set) {
namespace BookStack\Entities\Models;
-use BookStack\Sorting\SortSet;
+use BookStack\Sorting\SortRule;
use BookStack\Uploads\Image;
use Exception;
use Illuminate\Database\Eloquent\Factories\HasFactory;
* @property string $description
* @property int $image_id
* @property ?int $default_template_id
- * @property ?int $sort_set_id
+ * @property ?int $sort_rule_id
* @property Image|null $cover
* @property \Illuminate\Database\Eloquent\Collection $chapters
* @property \Illuminate\Database\Eloquent\Collection $pages
* @property \Illuminate\Database\Eloquent\Collection $directPages
* @property \Illuminate\Database\Eloquent\Collection $shelves
* @property ?Page $defaultTemplate
- * @property ?SortSet $sortSet
+ * @property ?SortRule $sortRule
*/
class Book extends Entity implements HasCoverImage
{
/**
* Get the sort set assigned to this book, if existing.
*/
- public function sortSet(): BelongsTo
+ public function sortRule(): BelongsTo
{
- return $this->belongsTo(SortSet::class);
+ return $this->belongsTo(SortRule::class);
}
/**
use BookStack\Entities\Tools\TrashCan;
use BookStack\Exceptions\ImageUploadException;
use BookStack\Facades\Activity;
-use BookStack\Sorting\SortSet;
+use BookStack\Sorting\SortRule;
use BookStack\Uploads\ImageRepo;
use Exception;
use Illuminate\Http\UploadedFile;
Activity::add(ActivityType::BOOK_CREATE, $book);
$defaultBookSortSetting = intval(setting('sorting-book-default', '0'));
- if ($defaultBookSortSetting && SortSet::query()->find($defaultBookSortSetting)) {
- $book->sort_set_id = $defaultBookSortSetting;
+ if ($defaultBookSortSetting && SortRule::query()->find($defaultBookSortSetting)) {
+ $book->sort_rule_id = $defaultBookSortSetting;
$book->save();
}
if ($request->filled('auto-sort')) {
$sortSetId = intval($request->get('auto-sort')) ?: null;
- if ($sortSetId && SortSet::query()->find($sortSetId) === null) {
+ if ($sortSetId && SortRule::query()->find($sortSetId) === null) {
$sortSetId = null;
}
- $book->sort_set_id = $sortSetId;
+ $book->sort_rule_id = $sortSetId;
$book->save();
$sorter->runBookAutoSort($book);
if (!$loggedActivityForBook) {
) {
}
- public function runBookAutoSortForAllWithSet(SortSet $set): void
+ public function runBookAutoSortForAllWithSet(SortRule $set): void
{
$set->books()->chunk(50, function ($books) {
foreach ($books as $book) {
*/
public function runBookAutoSort(Book $book): void
{
- $set = $book->sortSet;
+ $set = $book->sortRule;
if (!$set) {
return;
}
- $sortFunctions = array_map(function (SortSetOperation $op) {
+ $sortFunctions = array_map(function (SortRuleOperation $op) {
return $op->getSortFunction();
}, $set->getOperations());
* @property Carbon $created_at
* @property Carbon $updated_at
*/
-class SortSet extends Model implements Loggable
+class SortRule extends Model implements Loggable
{
use HasFactory;
/**
- * @return SortSetOperation[]
+ * @return SortRuleOperation[]
*/
public function getOperations(): array
{
- return SortSetOperation::fromSequence($this->sequence);
+ return SortRuleOperation::fromSequence($this->sequence);
}
/**
- * @param SortSetOperation[] $options
+ * @param SortRuleOperation[] $options
*/
public function setOperations(array $options): void
{
- $values = array_map(fn (SortSetOperation $opt) => $opt->value, $options);
+ $values = array_map(fn (SortRuleOperation $opt) => $opt->value, $options);
$this->sequence = implode(',', $values);
}
public function getUrl(): string
{
- return url("/settings/sorting/sets/{$this->id}");
+ return url("/settings/sorting/rules/{$this->id}");
}
public function books(): HasMany
use BookStack\Http\Controller;
use Illuminate\Http\Request;
-class SortSetController extends Controller
+class SortRuleController extends Controller
{
public function __construct()
{
public function create()
{
- $this->setPageTitle(trans('settings.sort_set_create'));
+ $this->setPageTitle(trans('settings.sort_rule_create'));
- return view('settings.sort-sets.create');
+ return view('settings.sort-rules.create');
}
public function store(Request $request)
'sequence' => ['required', 'string', 'min:1'],
]);
- $operations = SortSetOperation::fromSequence($request->input('sequence'));
+ $operations = SortRuleOperation::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();
+ $rule = new SortRule();
+ $rule->name = $request->input('name');
+ $rule->setOperations($operations);
+ $rule->save();
- $this->logActivity(ActivityType::SORT_SET_CREATE, $set);
+ $this->logActivity(ActivityType::SORT_RULE_CREATE, $rule);
return redirect('/settings/sorting');
}
public function edit(string $id)
{
- $set = SortSet::query()->findOrFail($id);
+ $rule = SortRule::query()->findOrFail($id);
- $this->setPageTitle(trans('settings.sort_set_edit'));
+ $this->setPageTitle(trans('settings.sort_rule_edit'));
- return view('settings.sort-sets.edit', ['set' => $set]);
+ return view('settings.sort-rules.edit', ['rule' => $rule]);
}
public function update(string $id, Request $request, BookSorter $bookSorter)
'sequence' => ['required', 'string', 'min:1'],
]);
- $set = SortSet::query()->findOrFail($id);
- $operations = SortSetOperation::fromSequence($request->input('sequence'));
+ $rule = SortRule::query()->findOrFail($id);
+ $operations = SortRuleOperation::fromSequence($request->input('sequence'));
if (count($operations) === 0) {
- return redirect($set->getUrl())->withInput()->withErrors(['sequence' => 'No operations set.']);
+ return redirect($rule->getUrl())->withInput()->withErrors(['sequence' => 'No operations set.']);
}
- $set->name = $request->input('name');
- $set->setOperations($operations);
- $changedSequence = $set->isDirty('sequence');
- $set->save();
+ $rule->name = $request->input('name');
+ $rule->setOperations($operations);
+ $changedSequence = $rule->isDirty('sequence');
+ $rule->save();
- $this->logActivity(ActivityType::SORT_SET_UPDATE, $set);
+ $this->logActivity(ActivityType::SORT_RULE_UPDATE, $rule);
if ($changedSequence) {
- $bookSorter->runBookAutoSortForAllWithSet($set);
+ $bookSorter->runBookAutoSortForAllWithSet($rule);
}
return redirect('/settings/sorting');
public function destroy(string $id, Request $request)
{
- $set = SortSet::query()->findOrFail($id);
+ $rule = SortRule::query()->findOrFail($id);
$confirmed = $request->input('confirm') === 'true';
- $booksAssigned = $set->books()->count();
+ $booksAssigned = $rule->books()->count();
$warnings = [];
if ($booksAssigned > 0) {
if ($confirmed) {
- $set->books()->update(['sort_set_id' => null]);
+ $rule->books()->update(['sort_rule_id' => null]);
} else {
- $warnings[] = trans('settings.sort_set_delete_warn_books', ['count' => $booksAssigned]);
+ $warnings[] = trans('settings.sort_rule_delete_warn_books', ['count' => $booksAssigned]);
}
}
if ($confirmed) {
setting()->remove('sorting-book-default');
} else {
- $warnings[] = trans('settings.sort_set_delete_warn_default');
+ $warnings[] = trans('settings.sort_rule_delete_warn_default');
}
}
if (count($warnings) > 0) {
- return redirect($set->getUrl() . '#delete')->withErrors(['delete' => $warnings]);
+ return redirect($rule->getUrl() . '#delete')->withErrors(['delete' => $warnings]);
}
- $set->delete();
- $this->logActivity(ActivityType::SORT_SET_DELETE, $set);
+ $rule->delete();
+ $this->logActivity(ActivityType::SORT_RULE_DELETE, $rule);
return redirect('/settings/sorting');
}
use Closure;
use Illuminate\Support\Str;
-enum SortSetOperation: string
+enum SortRuleOperation: string
{
case NameAsc = 'name_asc';
case NameDesc = 'name_desc';
$label = '';
if (str_ends_with($key, '_asc')) {
$key = substr($key, 0, -4);
- $label = trans('settings.sort_set_op_asc');
+ $label = trans('settings.sort_rule_op_asc');
} elseif (str_ends_with($key, '_desc')) {
$key = substr($key, 0, -5);
- $label = trans('settings.sort_set_op_desc');
+ $label = trans('settings.sort_rule_op_desc');
}
- $label = trans('settings.sort_set_op_' . $key) . ' ' . $label;
+ $label = trans('settings.sort_rule_op_' . $key) . ' ' . $label;
return trim($label);
}
}
/**
- * @return SortSetOperation[]
+ * @return SortRuleOperation[]
*/
public static function allExcluding(array $operations): array
{
- $all = SortSetOperation::cases();
- $filtered = array_filter($all, function (SortSetOperation $operation) use ($operations) {
+ $all = SortRuleOperation::cases();
+ $filtered = array_filter($all, function (SortRuleOperation $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[]
+ * @return SortRuleOperation[]
*/
public static function fromSequence(string $sequence): array
{
$strOptions = explode(',', $sequence);
- $options = array_map(fn ($val) => SortSetOperation::tryFrom($val), $strOptions);
+ $options = array_map(fn ($val) => SortRuleOperation::tryFrom($val), $strOptions);
return array_filter($options);
}
}
'slug' => Str::random(10),
'description' => $description,
'description_html' => '<p>' . e($description) . '</p>',
- 'sort_set_id' => null,
+ 'sort_rule_id' => null,
'default_template_id' => null,
];
}
namespace Database\Factories\Sorting;
-use BookStack\Sorting\SortSet;
-use BookStack\Sorting\SortSetOperation;
+use BookStack\Sorting\SortRule;
+use BookStack\Sorting\SortRuleOperation;
use Illuminate\Database\Eloquent\Factories\Factory;
-class SortSetFactory extends Factory
+class SortRuleFactory extends Factory
{
/**
* The name of the factory's corresponding model.
*
* @var string
*/
- protected $model = SortSet::class;
+ protected $model = SortRule::class;
/**
* Define the model's default state.
*/
public function definition(): array
{
- $cases = SortSetOperation::cases();
+ $cases = SortRuleOperation::cases();
$op = $cases[array_rand($cases)];
return [
'name' => $op->name . ' Sort',
*/
public function up(): void
{
- Schema::create('sort_sets', function (Blueprint $table) {
+ Schema::create('sort_rules', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->text('sequence');
*/
public function down(): void
{
- Schema::dropIfExists('sort_sets');
+ Schema::dropIfExists('sort_rules');
}
};
public function up(): void
{
Schema::table('books', function (Blueprint $table) {
- $table->unsignedInteger('sort_set_id')->nullable()->default(null);
+ $table->unsignedInteger('sort_rule_id')->nullable()->default(null);
});
}
public function down(): void
{
Schema::table('books', function (Blueprint $table) {
- $table->dropColumn('sort_set_id');
+ $table->dropColumn('sort_rule_id');
});
}
};
'books_search_this' => 'Search this book',
'books_navigation' => 'Book Navigation',
'books_sort' => 'Sort Book Contents',
- 'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books. Optionally an auto sort option can be set to automatically sort this book\'s contents upon changes.',
+ 'books_sort_desc' => 'Move chapters and pages within a book to reorganise its contents. Other books can be added which allows easy moving of chapters and pages between books. Optionally an auto sort rule can be set to automatically sort this book\'s contents upon changes.',
'books_sort_auto_sort' => 'Auto Sort Option',
'books_sort_auto_sort_active' => 'Auto Sort Active: :sortName',
'books_sort_named' => 'Sort Book :bookName',
// Sorting Settings
'sorting' => 'Sorting',
'sorting_book_default' => 'Default Book Sort',
- '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_assigned_to_x_books' => 'Assigned to :count Book|Assigned to :count Books',
- '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. Books using this sort will revert to manual sorting.',
- 'sort_set_delete_warn_books' => 'This sort set is currently used on :count book(s). Are you sure you want to delete this?',
- 'sort_set_delete_warn_default' => 'This sort set is currently used as the default for books. Are you sure you want to delete this?',
- '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_available_operations_empty' => 'No operations remaining',
- 'sort_set_configured_operations' => 'Configured Operations',
- 'sort_set_configured_operations_empty' => 'Drag/add operations from the "Available Operations" list',
- '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',
+ 'sorting_book_default_desc' => 'Select the default sort role to apply to new books. This won\'t affect existing books, and can be overridden per-book.',
+ 'sorting_rules' => 'Sort Rules',
+ 'sorting_rules_desc' => 'These are predefined sorting operations which can be applied to content in the system.',
+ 'sort_rule_assigned_to_x_books' => 'Assigned to :count Book|Assigned to :count Books',
+ 'sort_rule_create' => 'Create Sort Rule',
+ 'sort_rule_edit' => 'Edit Sort Rule',
+ 'sort_rule_delete' => 'Delete Sort Rule',
+ 'sort_rule_delete_desc' => 'Remove this sort rule from the system. Books using this sort will revert to manual sorting.',
+ 'sort_rule_delete_warn_books' => 'This sort rule is currently used on :count book(s). Are you sure you want to delete this?',
+ 'sort_rule_delete_warn_default' => 'This sort rule is currently used as the default for books. Are you sure you want to delete this?',
+ 'sort_rule_details' => 'Sort Rule Details',
+ 'sort_rule_details_desc' => 'Set a name for this sort rule, which will appear in lists when users are selecting a sort.',
+ 'sort_rule_operations' => 'Sort Operations',
+ 'sort_rule_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_rule_available_operations' => 'Available Operations',
+ 'sort_rule_available_operations_empty' => 'No operations remaining',
+ 'sort_rule_configured_operations' => 'Configured Operations',
+ 'sort_rule_configured_operations_empty' => 'Drag/add operations from the "Available Operations" list',
+ 'sort_rule_op_asc' => '(Asc)',
+ 'sort_rule_op_desc' => '(Desc)',
+ 'sort_rule_op_name' => 'Name - Alphabetical',
+ 'sort_rule_op_name_numeric' => 'Name - Numeric',
+ 'sort_rule_op_created_date' => 'Created Date',
+ 'sort_rule_op_updated_date' => 'Updated Date',
+ 'sort_rule_op_chapters_first' => 'Chapters First',
+ 'sort_rule_op_chapters_last' => 'Chapters Last',
// Maintenance settings
'maint' => 'Maintenance',
export {Shortcuts} from './shortcuts';
export {ShortcutInput} from './shortcut-input';
export {SortableList} from './sortable-list';
-export {SortSetManager} from './sort-set-manager'
+export {SortRuleManager} from './sort-rule-manager'
export {SubmitOnChange} from './submit-on-change';
export {Tabs} from './tabs';
export {TagManager} from './tag-manager';
import {buildListActions, sortActionClickListener} from "../services/dual-lists";
-export class SortSetManager extends Component {
+export class SortRuleManager extends Component {
protected input!: HTMLInputElement;
protected configuredList!: HTMLElement;
const scrollBoxes = [this.configuredList, this.availableList];
for (const scrollBox of scrollBoxes) {
new Sortable(scrollBox, {
- group: 'sort-set-operations',
+ group: 'sort-rule-operations',
ghostClass: 'primary-background-light',
handle: '.handle',
animation: 150,
<span>{{ $book->name }}</span>
</div>
<div class="flex-container-row items-center text-book">
- @if($book->sortSet)
- <span title="{{ trans('entities.books_sort_auto_sort_active', ['sortName' => $book->sortSet->name]) }}">@icon('auto-sort')</span>
+ @if($book->sortRule)
+ <span title="{{ trans('entities.books_sort_auto_sort_active', ['sortName' => $book->sortRule->name]) }}">@icon('auto-sort')</span>
@endif
</div>
</h5>
</summary>
<div class="sort-box-options pb-sm">
- <button type="button" data-sort="name" class="button outline small">{{ trans('entities.books_sort_name') }}</button>
- <button type="button" data-sort="created" class="button outline small">{{ trans('entities.books_sort_created') }}</button>
- <button type="button" data-sort="updated" class="button outline small">{{ trans('entities.books_sort_updated') }}</button>
- <button type="button" data-sort="chaptersFirst" class="button outline small">{{ trans('entities.books_sort_chapters_first') }}</button>
- <button type="button" data-sort="chaptersLast" class="button outline small">{{ trans('entities.books_sort_chapters_last') }}</button>
+ <button type="button" data-sort="name"
+ class="button outline small">{{ trans('entities.books_sort_name') }}</button>
+ <button type="button" data-sort="created"
+ class="button outline small">{{ trans('entities.books_sort_created') }}</button>
+ <button type="button" data-sort="updated"
+ class="button outline small">{{ trans('entities.books_sort_updated') }}</button>
+ <button type="button" data-sort="chaptersFirst"
+ class="button outline small">{{ trans('entities.books_sort_chapters_first') }}</button>
+ <button type="button" data-sort="chaptersLast"
+ class="button outline small">{{ trans('entities.books_sort_chapters_last') }}</button>
</div>
<ul class="sortable-page-list sort-list">
<p class="text-muted flex min-width-s mb-none">{{ trans('entities.books_sort_desc') }}</p>
<div class="min-width-s">
@php
- $autoSortVal = intval(old('auto-sort') ?? $book->sort_set_id ?? 0);
+ $autoSortVal = intval(old('auto-sort') ?? $book->sort_rule_id ?? 0);
@endphp
<label for="auto-sort">{{ trans('entities.books_sort_auto_sort') }}</label>
<select id="auto-sort"
name="auto-sort"
form="sort-form"
class="{{ $errors->has('auto-sort') ? 'neg' : '' }}">
- <option value="0" @if($autoSortVal === 0) selected @endif>-- {{ trans('common.none') }} --</option>
- @foreach(\BookStack\Sorting\SortSet::allByName() as $set)
- <option value="{{$set->id}}"
- @if($autoSortVal === $set->id) selected @endif
+ <option value="0" @if($autoSortVal === 0) selected @endif>-- {{ trans('common.none') }}
+ --
+ </option>
+ @foreach(\BookStack\Sorting\SortRule::allByName() as $rule)
+ <option value="{{$rule->id}}"
+ @if($autoSortVal === $rule->id) selected @endif
>
- {{ $set->name }}
+ {{ $rule->name }}
</option>
@endforeach
</select>
@extends('settings.layout')
@php
- $sortSets = \BookStack\Sorting\SortSet::allByName();
+ $sortRules = \BookStack\Sorting\SortRule::allByName();
@endphp
@section('card')
<option value="0" @if(intval(setting('sorting-book-default', '0')) === 0) selected @endif>
-- {{ trans('common.none') }} --
</option>
- @foreach($sortSets as $set)
+ @foreach($sortRules as $set)
<option value="{{$set->id}}"
@if(intval(setting('sorting-book-default', '0')) === $set->id) selected @endif
>
<div class="card content-wrap auto-height">
<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>
+ <h2 class="list-heading">{{ trans('settings.sorting_rules') }}</h2>
+ <p class="text-muted">{{ trans('settings.sorting_rules_desc') }}</p>
</div>
<div>
- <a href="{{ url('/settings/sorting/sets/new') }}" class="button outline">{{ trans('settings.sort_set_create') }}</a>
+ <a href="{{ url('/settings/sorting/rules/new') }}"
+ class="button outline">{{ trans('settings.sort_rule_create') }}</a>
</div>
</div>
- @if(empty($sortSets))
+ @if(empty($sortRules))
<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])
+ @foreach($sortRules as $rule)
+ @include('settings.sort-rules.parts.sort-rule-list-item', ['rule' => $rule])
@endforeach
</div>
@endif
@include('settings.parts.navbar', ['selected' => 'settings'])
<div class="card content-wrap auto-height">
- <h1 class="list-heading">{{ trans('settings.sort_set_create') }}</h1>
+ <h1 class="list-heading">{{ trans('settings.sort_rule_create') }}</h1>
- <form action="{{ url("/settings/sorting/sets") }}" method="POST">
+ <form action="{{ url("/settings/sorting/rules") }}" method="POST">
{{ csrf_field() }}
- @include('settings.sort-sets.parts.form', ['model' => null])
+ @include('settings.sort-rules.parts.form', ['model' => null])
<div class="form-group text-right">
<a href="{{ url("/settings/sorting") }}" class="button outline">{{ trans('common.cancel') }}</a>
@include('settings.parts.navbar', ['selected' => 'settings'])
<div class="card content-wrap auto-height">
- <h1 class="list-heading">{{ trans('settings.sort_set_edit') }}</h1>
+ <h1 class="list-heading">{{ trans('settings.sort_rule_edit') }}</h1>
- <form action="{{ $set->getUrl() }}" method="POST">
+ <form action="{{ $rule->getUrl() }}" method="POST">
{{ method_field('PUT') }}
{{ csrf_field() }}
- @include('settings.sort-sets.parts.form', ['model' => $set])
+ @include('settings.sort-rules.parts.form', ['model' => $rule])
<div class="form-group text-right">
<a href="{{ url("/settings/sorting") }}" class="button outline">{{ trans('common.cancel') }}</a>
<div id="delete" class="card content-wrap auto-height">
<div class="flex-container-row items-center gap-l">
<div class="mb-m">
- <h2 class="list-heading">{{ trans('settings.sort_set_delete') }}</h2>
- <p class="text-muted mb-xs">{{ trans('settings.sort_set_delete_desc') }}</p>
+ <h2 class="list-heading">{{ trans('settings.sort_rule_delete') }}</h2>
+ <p class="text-muted mb-xs">{{ trans('settings.sort_rule_delete_desc') }}</p>
@if($errors->has('delete'))
@foreach($errors->get('delete') as $error)
<p class="text-neg mb-xs">{{ $error }}</p>
@endif
</div>
<div class="flex">
- <form action="{{ $set->getUrl() }}" method="POST">
+ <form action="{{ $rule->getUrl() }}" method="POST">
{{ method_field('DELETE') }}
{{ csrf_field() }}
<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>
+ <label class="setting-list-label">{{ trans('settings.sort_rule_details') }}</label>
+ <p class="text-muted text-small">{{ trans('settings.sort_rule_details_desc') }}</p>
</div>
<div>
<div class="form-group">
</div>
</div>
- <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>
+ <div component="sort-rule-manager">
+ <label class="setting-list-label">{{ trans('settings.sort_rule_operations') }}</label>
+ <p class="text-muted text-small">{{ trans('settings.sort_rule_operations_desc') }}</p>
@include('form.errors', ['name' => 'sequence'])
- <input refs="sort-set-manager@input" type="hidden" name="sequence"
+ <input refs="sort-rule-manager@input" type="hidden" name="sequence"
value="{{ old('sequence') ?? $model?->sequence ?? '' }}">
@php
- $configuredOps = old('sequence') ? \BookStack\Sorting\SortSetOperation::fromSequence(old('sequence')) : ($model?->getOperations() ?? []);
+ $configuredOps = old('sequence') ? \BookStack\Sorting\SortRuleOperation::fromSequence(old('sequence')) : ($model?->getOperations() ?? []);
@endphp
<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-manager@configured-operations-list"
- aria-labelledby="sort-set-configured-operations"
+ id="sort-rule-configured-operations">{{ trans('settings.sort_rule_configured_operations') }}</label>
+ <ul refs="sort-rule-manager@configured-operations-list"
+ aria-labelledby="sort-rule-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>
+ <li class="text-muted empty-state px-m py-s italic text-small">{{ trans('settings.sort_rule_configured_operations_empty') }}</li>
@foreach($configuredOps as $operation)
- @include('settings.sort-sets.parts.operation', ['operation' => $operation])
+ @include('settings.sort-rules.parts.operation', ['operation' => $operation])
@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-manager@available-operations-list"
- aria-labelledby="sort-set-available-operations"
+ id="sort-rule-available-operations">{{ trans('settings.sort_rule_available_operations') }}</label>
+ <ul refs="sort-rule-manager@available-operations-list"
+ aria-labelledby="sort-rule-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($configuredOps) as $operation)
- @include('settings.sort-sets.parts.operation', ['operation' => $operation])
+ <li class="text-muted empty-state px-m py-s italic text-small">{{ trans('settings.sort_rule_available_operations_empty') }}</li>
+ @foreach(\BookStack\Sorting\SortRuleOperation::allExcluding($configuredOps) as $operation)
+ @include('settings.sort-rules.parts.operation', ['operation' => $operation])
@endforeach
</ul>
</div>
<div class="item-list-row flex-container-row py-xs px-m gap-m items-center">
<div class="py-xs flex">
- <a href="{{ $set->getUrl() }}">{{ $set->name }}</a>
+ <a href="{{ $rule->getUrl() }}">{{ $rule->name }}</a>
</div>
<div class="px-m text-small text-muted ml-auto">
- {{ implode(', ', array_map(fn ($op) => $op->getLabel(), $set->getOperations())) }}
+ {{ implode(', ', array_map(fn ($op) => $op->getLabel(), $rule->getOperations())) }}
</div>
<div>
- <span title="{{ trans_choice('settings.sort_set_assigned_to_x_books', $set->books_count ?? 0) }}"
- class="flex fill-area min-width-xxs bold text-right text-book"><span class="opacity-60">@icon('book')</span>{{ $set->books_count ?? 0 }}</span>
+ <span title="{{ trans_choice('settings.sort_rule_assigned_to_x_books', $rule->books_count ?? 0) }}"
+ class="flex fill-area min-width-xxs bold text-right text-book"><span class="opacity-60">@icon('book')</span>{{ $rule->books_count ?? 0 }}</span>
</div>
</div>
\ No newline at end of file
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']);
+ // Sort Rules
+ Route::get('/settings/sorting/rules/new', [SortingControllers\SortRuleController::class, 'create']);
+ Route::post('/settings/sorting/rules', [SortingControllers\SortRuleController::class, 'store']);
+ Route::get('/settings/sorting/rules/{id}', [SortingControllers\SortRuleController::class, 'edit']);
+ Route::put('/settings/sorting/rules/{id}', [SortingControllers\SortRuleController::class, 'update']);
+ Route::delete('/settings/sorting/rules/{id}', [SortingControllers\SortRuleController::class, 'destroy']);
// Settings
Route::get('/settings', [SettingControllers\SettingController::class, 'index'])->name('settings');
namespace Commands;
use BookStack\Entities\Models\Book;
-use BookStack\Sorting\SortSet;
+use BookStack\Sorting\SortRule;
use Tests\TestCase;
class AssignSortSetCommandTest extends TestCase
{
public function test_no_given_sort_set_lists_options()
{
- $sortSets = SortSet::factory()->createMany(10);
+ $sortSets = SortRule::factory()->createMany(10);
$commandRun = $this->artisan('bookstack:assign-sort-set')
->expectsOutputToContain('Sort set ID required!')
public function test_confirmation_required()
{
- $sortSet = SortSet::factory()->create();
+ $sortSet = SortRule::factory()->create();
$this->artisan("bookstack:assign-sort-set {$sortSet->id} --all-books")
->expectsConfirmation('Are you sure you want to continue?', 'no')
public function test_assign_to_all_books()
{
- $sortSet = SortSet::factory()->create();
+ $sortSet = SortRule::factory()->create();
$booksWithoutSort = Book::query()->whereNull('sort_set_id')->count();
$this->assertGreaterThan(0, $booksWithoutSort);
{
$totalBooks = Book::query()->count();
$book = $this->entities->book();
- $sortSetA = SortSet::factory()->create();
- $sortSetB = SortSet::factory()->create();
- $book->sort_set_id = $sortSetA->id;
+ $sortSetA = SortRule::factory()->create();
+ $sortSetB = SortRule::factory()->create();
+ $book->sort_rule_id = $sortSetA->id;
$book->save();
$booksWithoutSort = Book::query()->whereNull('sort_set_id')->count();
public function test_assign_to_all_books_with_sort()
{
$book = $this->entities->book();
- $sortSetA = SortSet::factory()->create();
- $sortSetB = SortSet::factory()->create();
- $book->sort_set_id = $sortSetA->id;
+ $sortSetA = SortRule::factory()->create();
+ $sortSetB = SortRule::factory()->create();
+ $book->sort_rule_id = $sortSetA->id;
$book->save();
$this->artisan("bookstack:assign-sort-set {$sortSetB->id} --books-with-sort={$sortSetA->id}")
->assertExitCode(0);
$book->refresh();
- $this->assertEquals($sortSetB->id, $book->sort_set_id);
+ $this->assertEquals($sortSetB->id, $book->sort_rule_id);
$this->assertEquals(1, $sortSetB->books()->count());
}
]);
$resp = $this->asAdmin()->get('/pages/recently-updated');
- $this->withHtml($resp)->assertElementContains('.entity-list .page:nth-child(1)', 'Updated 0 seconds ago by ' . $user->name);
+ $this->withHtml($resp)->assertElementContains('.entity-list .page:nth-child(1) small', 'by ' . $user->name);
}
public function test_recently_updated_pages_view_shows_parent_chain()
use BookStack\Entities\Models\Chapter;
use BookStack\Entities\Models\Page;
use BookStack\Entities\Repos\PageRepo;
-use BookStack\Sorting\SortSet;
+use BookStack\Sorting\SortRule;
use Tests\TestCase;
class BookSortTest extends TestCase
public function test_book_sort_item_shows_auto_sort_status()
{
- $sort = SortSet::factory()->create(['name' => 'My sort']);
+ $sort = SortRule::factory()->create(['name' => 'My sort']);
$book = $this->entities->book();
$resp = $this->asAdmin()->get($book->getUrl('/sort-item'));
$this->withHtml($resp)->assertElementNotExists("span[title='Auto Sort Active: My sort']");
- $book->sort_set_id = $sort->id;
+ $book->sort_rule_id = $sort->id;
$book->save();
$resp = $this->asAdmin()->get($book->getUrl('/sort-item'));
public function test_auto_sort_options_shown_on_sort_page()
{
- $sort = SortSet::factory()->create();
+ $sort = SortRule::factory()->create();
$book = $this->entities->book();
$resp = $this->asAdmin()->get($book->getUrl('/sort'));
public function test_auto_sort_option_submit_saves_to_book()
{
- $sort = SortSet::factory()->create();
+ $sort = SortRule::factory()->create();
$book = $this->entities->book();
$bookPage = $book->pages()->first();
$bookPage->priority = 10000;
$book->refresh();
$bookPage->refresh();
- $this->assertEquals($sort->id, $book->sort_set_id);
+ $this->assertEquals($sort->id, $book->sort_rule_id);
$this->assertNotEquals(10000, $bookPage->priority);
$resp = $this->get($book->getUrl('/sort'));
use BookStack\Activity\ActivityType;
use BookStack\Entities\Models\Book;
-use BookStack\Sorting\SortSet;
+use BookStack\Sorting\SortRule;
use Tests\Api\TestsApi;
use Tests\TestCase;
-class SortSetTest extends TestCase
+class SortRuleTest extends TestCase
{
use TestsApi;
public function test_manage_settings_permission_required()
{
- $set = SortSet::factory()->create();
+ $rule = SortRule::factory()->create();
$user = $this->users->viewer();
$this->actingAs($user);
$actions = [
['GET', '/settings/sorting'],
- ['POST', '/settings/sorting/sets'],
- ['GET', "/settings/sorting/sets/{$set->id}"],
- ['PUT', "/settings/sorting/sets/{$set->id}"],
- ['DELETE', "/settings/sorting/sets/{$set->id}"],
+ ['POST', '/settings/sorting/rules'],
+ ['GET', "/settings/sorting/rules/{$rule->id}"],
+ ['PUT', "/settings/sorting/rules/{$rule->id}"],
+ ['DELETE', "/settings/sorting/rules/{$rule->id}"],
];
foreach ($actions as [$method, $path]) {
public function test_create_flow()
{
$resp = $this->asAdmin()->get('/settings/sorting');
- $this->withHtml($resp)->assertLinkExists(url('/settings/sorting/sets/new'));
+ $this->withHtml($resp)->assertLinkExists(url('/settings/sorting/rules/new'));
- $resp = $this->get('/settings/sorting/sets/new');
- $this->withHtml($resp)->assertElementExists('form[action$="/settings/sorting/sets"] input[name="name"]');
+ $resp = $this->get('/settings/sorting/rules/new');
+ $this->withHtml($resp)->assertElementExists('form[action$="/settings/sorting/rules"] input[name="name"]');
$resp->assertSeeText('Name - Alphabetical (Asc)');
$details = ['name' => 'My new sort', 'sequence' => 'name_asc'];
- $resp = $this->post('/settings/sorting/sets', $details);
+ $resp = $this->post('/settings/sorting/rules', $details);
$resp->assertRedirect('/settings/sorting');
- $this->assertActivityExists(ActivityType::SORT_SET_CREATE);
- $this->assertDatabaseHas('sort_sets', $details);
+ $this->assertActivityExists(ActivityType::SORT_RULE_CREATE);
+ $this->assertDatabaseHas('sort_rules', $details);
}
public function test_listing_in_settings()
{
- $set = SortSet::factory()->create(['name' => 'My super sort set', 'sequence' => 'name_asc']);
+ $rule = SortRule::factory()->create(['name' => 'My super sort rule', 'sequence' => 'name_asc']);
$books = Book::query()->limit(5)->get();
foreach ($books as $book) {
- $book->sort_set_id = $set->id;
+ $book->sort_rule_id = $rule->id;
$book->save();
}
$resp = $this->asAdmin()->get('/settings/sorting');
- $resp->assertSeeText('My super sort set');
+ $resp->assertSeeText('My super sort rule');
$resp->assertSeeText('Name - Alphabetical (Asc)');
$this->withHtml($resp)->assertElementContains('.item-list-row [title="Assigned to 5 Books"]', '5');
}
public function test_update_flow()
{
- $set = SortSet::factory()->create(['name' => 'My sort set to update', 'sequence' => 'name_asc']);
+ $rule = SortRule::factory()->create(['name' => 'My sort rule to update', 'sequence' => 'name_asc']);
- $resp = $this->asAdmin()->get("/settings/sorting/sets/{$set->id}");
+ $resp = $this->asAdmin()->get("/settings/sorting/rules/{$rule->id}");
$respHtml = $this->withHtml($resp);
$respHtml->assertElementContains('.configured-option-list', 'Name - Alphabetical (Asc)');
$respHtml->assertElementNotContains('.available-option-list', 'Name - Alphabetical (Asc)');
$updateData = ['name' => 'My updated sort', 'sequence' => 'name_desc,chapters_last'];
- $resp = $this->put("/settings/sorting/sets/{$set->id}", $updateData);
+ $resp = $this->put("/settings/sorting/rules/{$rule->id}", $updateData);
$resp->assertRedirect('/settings/sorting');
- $this->assertActivityExists(ActivityType::SORT_SET_UPDATE);
- $this->assertDatabaseHas('sort_sets', $updateData);
+ $this->assertActivityExists(ActivityType::SORT_RULE_UPDATE);
+ $this->assertDatabaseHas('sort_rules', $updateData);
}
public function test_update_triggers_resort_on_assigned_books()
{
$book = $this->entities->bookHasChaptersAndPages();
$chapter = $book->chapters()->first();
- $set = SortSet::factory()->create(['name' => 'My sort set to update', 'sequence' => 'name_asc']);
- $book->sort_set_id = $set->id;
+ $rule = SortRule::factory()->create(['name' => 'My sort rule to update', 'sequence' => 'name_asc']);
+ $book->sort_rule_id = $rule->id;
$book->save();
$chapter->priority = 10000;
$chapter->save();
- $resp = $this->asAdmin()->put("/settings/sorting/sets/{$set->id}", ['name' => $set->name, 'sequence' => 'chapters_last']);
+ $resp = $this->asAdmin()->put("/settings/sorting/rules/{$rule->id}", ['name' => $rule->name, 'sequence' => 'chapters_last']);
$resp->assertRedirect('/settings/sorting');
$chapter->refresh();
public function test_delete_flow()
{
- $set = SortSet::factory()->create();
+ $rule = SortRule::factory()->create();
- $resp = $this->asAdmin()->get("/settings/sorting/sets/{$set->id}");
- $resp->assertSeeText('Delete Sort Set');
+ $resp = $this->asAdmin()->get("/settings/sorting/rules/{$rule->id}");
+ $resp->assertSeeText('Delete Sort Rule');
- $resp = $this->delete("settings/sorting/sets/{$set->id}");
+ $resp = $this->delete("settings/sorting/rules/{$rule->id}");
$resp->assertRedirect('/settings/sorting');
- $this->assertActivityExists(ActivityType::SORT_SET_DELETE);
- $this->assertDatabaseMissing('sort_sets', ['id' => $set->id]);
+ $this->assertActivityExists(ActivityType::SORT_RULE_DELETE);
+ $this->assertDatabaseMissing('sort_rules', ['id' => $rule->id]);
}
public function test_delete_requires_confirmation_if_books_assigned()
{
- $set = SortSet::factory()->create();
+ $rule = SortRule::factory()->create();
$books = Book::query()->limit(5)->get();
foreach ($books as $book) {
- $book->sort_set_id = $set->id;
+ $book->sort_rule_id = $rule->id;
$book->save();
}
- $resp = $this->asAdmin()->get("/settings/sorting/sets/{$set->id}");
- $resp->assertSeeText('Delete Sort Set');
+ $resp = $this->asAdmin()->get("/settings/sorting/rules/{$rule->id}");
+ $resp->assertSeeText('Delete Sort Rule');
- $resp = $this->delete("settings/sorting/sets/{$set->id}");
- $resp->assertRedirect("/settings/sorting/sets/{$set->id}#delete");
+ $resp = $this->delete("settings/sorting/rules/{$rule->id}");
+ $resp->assertRedirect("/settings/sorting/rules/{$rule->id}#delete");
$resp = $this->followRedirects($resp);
- $resp->assertSeeText('This sort set is currently used on 5 book(s). Are you sure you want to delete this?');
- $this->assertDatabaseHas('sort_sets', ['id' => $set->id]);
+ $resp->assertSeeText('This sort rule is currently used on 5 book(s). Are you sure you want to delete this?');
+ $this->assertDatabaseHas('sort_rules', ['id' => $rule->id]);
- $resp = $this->delete("settings/sorting/sets/{$set->id}", ['confirm' => 'true']);
+ $resp = $this->delete("settings/sorting/rules/{$rule->id}", ['confirm' => 'true']);
$resp->assertRedirect('/settings/sorting');
- $this->assertDatabaseMissing('sort_sets', ['id' => $set->id]);
- $this->assertDatabaseMissing('books', ['sort_set_id' => $set->id]);
+ $this->assertDatabaseMissing('sort_rules', ['id' => $rule->id]);
+ $this->assertDatabaseMissing('books', ['sort_rule_id' => $rule->id]);
}
public function test_page_create_triggers_book_sort()
{
$book = $this->entities->bookHasChaptersAndPages();
- $set = SortSet::factory()->create(['sequence' => 'name_asc,chapters_first']);
- $book->sort_set_id = $set->id;
+ $rule = SortRule::factory()->create(['sequence' => 'name_asc,chapters_first']);
+ $book->sort_rule_id = $rule->id;
$book->save();
$resp = $this->actingAsApiEditor()->post("/api/pages", [
public function test_name_numeric_ordering()
{
$book = Book::factory()->create();
- $set = SortSet::factory()->create(['sequence' => 'name_numeric_asc']);
- $book->sort_set_id = $set->id;
+ $rule = SortRule::factory()->create(['sequence' => 'name_numeric_asc']);
+ $book->sort_rule_id = $rule->id;
$book->save();
$this->permissions->regenerateForEntity($book);