mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-03-14 20:32:49 +00:00
Sorting: Renamed sort set to sort rule
Renamed based on feedback from Tim and Script on Discord. Also fixed flaky test
This commit is contained in:
parent
a208c46b62
commit
b9306a9029
30 changed files with 232 additions and 224 deletions
app
Activity
Console/Commands
Entities
Sorting
database
factories
migrations
lang/en
resources
js/components
views
books
settings
routes
tests
|
@ -71,9 +71,9 @@ class ActivityType
|
|||
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.
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace BookStack\Console\Commands;
|
|||
|
||||
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
|
||||
|
@ -37,7 +37,7 @@ 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')) {
|
||||
|
@ -87,7 +87,7 @@ class AssignSortSetCommand extends Command
|
|||
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) {
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
namespace BookStack\Entities\Models;
|
||||
|
||||
use BookStack\Sorting\SortSet;
|
||||
use BookStack\Sorting\SortRule;
|
||||
use BookStack\Uploads\Image;
|
||||
use Exception;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
|
@ -17,14 +17,14 @@ use Illuminate\Support\Collection;
|
|||
* @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
|
||||
{
|
||||
|
@ -88,9 +88,9 @@ 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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,7 +8,7 @@ use BookStack\Entities\Models\Book;
|
|||
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;
|
||||
|
@ -35,8 +35,8 @@ class BookRepo
|
|||
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();
|
||||
}
|
||||
|
||||
|
|
|
@ -69,10 +69,10 @@ class BookSortController extends Controller
|
|||
|
||||
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) {
|
||||
|
|
|
@ -16,7 +16,7 @@ class BookSorter
|
|||
) {
|
||||
}
|
||||
|
||||
public function runBookAutoSortForAllWithSet(SortSet $set): void
|
||||
public function runBookAutoSortForAllWithSet(SortRule $set): void
|
||||
{
|
||||
$set->books()->chunk(50, function ($books) {
|
||||
foreach ($books as $book) {
|
||||
|
@ -32,12 +32,12 @@ class BookSorter
|
|||
*/
|
||||
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());
|
||||
|
||||
|
|
|
@ -17,24 +17,24 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
|||
* @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);
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ class SortSet extends Model implements Loggable
|
|||
|
||||
public function getUrl(): string
|
||||
{
|
||||
return url("/settings/sorting/sets/{$this->id}");
|
||||
return url("/settings/sorting/rules/{$this->id}");
|
||||
}
|
||||
|
||||
public function books(): HasMany
|
|
@ -6,7 +6,7 @@ use BookStack\Activity\ActivityType;
|
|||
use BookStack\Http\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class SortSetController extends Controller
|
||||
class SortRuleController extends Controller
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
|
@ -15,9 +15,9 @@ class SortSetController extends Controller
|
|||
|
||||
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)
|
||||
|
@ -27,28 +27,28 @@ class SortSetController extends Controller
|
|||
'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)
|
||||
|
@ -58,21 +58,21 @@ class SortSetController extends Controller
|
|||
'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');
|
||||
|
@ -80,16 +80,16 @@ class SortSetController extends Controller
|
|||
|
||||
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]);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -98,16 +98,16 @@ class SortSetController extends Controller
|
|||
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');
|
||||
}
|
|
@ -5,7 +5,7 @@ namespace BookStack\Sorting;
|
|||
use Closure;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
enum SortSetOperation: string
|
||||
enum SortRuleOperation: string
|
||||
{
|
||||
case NameAsc = 'name_asc';
|
||||
case NameDesc = 'name_desc';
|
||||
|
@ -26,13 +26,13 @@ enum SortSetOperation: string
|
|||
$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);
|
||||
}
|
||||
|
||||
|
@ -43,12 +43,12 @@ enum SortSetOperation: string
|
|||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
@ -57,12 +57,12 @@ enum SortSetOperation: string
|
|||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
|
@ -27,7 +27,7 @@ class BookFactory extends Factory
|
|||
'slug' => Str::random(10),
|
||||
'description' => $description,
|
||||
'description_html' => '<p>' . e($description) . '</p>',
|
||||
'sort_set_id' => null,
|
||||
'sort_rule_id' => null,
|
||||
'default_template_id' => null,
|
||||
];
|
||||
}
|
||||
|
|
|
@ -2,25 +2,25 @@
|
|||
|
||||
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',
|
|
@ -11,7 +11,7 @@ return new class extends Migration
|
|||
*/
|
||||
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');
|
||||
|
@ -24,6 +24,6 @@ return new class extends Migration
|
|||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('sort_sets');
|
||||
Schema::dropIfExists('sort_rules');
|
||||
}
|
||||
};
|
|
@ -12,7 +12,7 @@ return new class extends Migration
|
|||
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);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ return new class extends Migration
|
|||
public function down(): void
|
||||
{
|
||||
Schema::table('books', function (Blueprint $table) {
|
||||
$table->dropColumn('sort_set_id');
|
||||
$table->dropColumn('sort_rule_id');
|
||||
});
|
||||
}
|
||||
};
|
|
@ -166,7 +166,7 @@ return [
|
|||
'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',
|
||||
|
|
|
@ -77,32 +77,32 @@ return [
|
|||
// 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',
|
||||
|
|
|
@ -50,7 +50,7 @@ export {ShelfSort} from './shelf-sort';
|
|||
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';
|
||||
|
|
|
@ -3,7 +3,7 @@ import Sortable from "sortablejs";
|
|||
import {buildListActions, sortActionClickListener} from "../services/dual-lists";
|
||||
|
||||
|
||||
export class SortSetManager extends Component {
|
||||
export class SortRuleManager extends Component {
|
||||
|
||||
protected input!: HTMLInputElement;
|
||||
protected configuredList!: HTMLElement;
|
||||
|
@ -25,7 +25,7 @@ export class SortSetManager extends Component {
|
|||
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,
|
|
@ -9,18 +9,23 @@
|
|||
<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">
|
||||
|
||||
|
|
|
@ -23,19 +23,21 @@
|
|||
<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>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@extends('settings.layout')
|
||||
|
||||
@php
|
||||
$sortSets = \BookStack\Sorting\SortSet::allByName();
|
||||
$sortRules = \BookStack\Sorting\SortRule::allByName();
|
||||
@endphp
|
||||
|
||||
@section('card')
|
||||
|
@ -23,7 +23,7 @@
|
|||
<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
|
||||
>
|
||||
|
@ -46,20 +46,21 @@
|
|||
<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
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
@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>
|
|
@ -7,13 +7,13 @@
|
|||
@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>
|
||||
|
@ -25,8 +25,8 @@
|
|||
<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>
|
||||
|
@ -34,7 +34,7 @@
|
|||
@endif
|
||||
</div>
|
||||
<div class="flex">
|
||||
<form action="{{ $set->getUrl() }}" method="POST">
|
||||
<form action="{{ $rule->getUrl() }}" method="POST">
|
||||
{{ method_field('DELETE') }}
|
||||
{{ csrf_field() }}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
<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">
|
||||
|
@ -12,42 +12,42 @@
|
|||
</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>
|
|
@ -1,12 +1,12 @@
|
|||
<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>
|
|
@ -295,12 +295,12 @@ 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']);
|
||||
// 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');
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
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!')
|
||||
|
@ -37,7 +37,7 @@ class AssignSortSetCommandTest extends TestCase
|
|||
|
||||
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')
|
||||
|
@ -49,7 +49,7 @@ class AssignSortSetCommandTest extends TestCase
|
|||
|
||||
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);
|
||||
|
||||
|
@ -67,9 +67,9 @@ class AssignSortSetCommandTest extends TestCase
|
|||
{
|
||||
$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();
|
||||
|
@ -88,9 +88,9 @@ class AssignSortSetCommandTest extends TestCase
|
|||
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}")
|
||||
|
@ -99,7 +99,7 @@ class AssignSortSetCommandTest extends TestCase
|
|||
->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());
|
||||
}
|
||||
|
||||
|
|
|
@ -300,7 +300,7 @@ class PageTest extends TestCase
|
|||
]);
|
||||
|
||||
$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()
|
||||
|
|
|
@ -5,7 +5,7 @@ namespace Sorting;
|
|||
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
|
||||
|
@ -223,13 +223,13 @@ 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'));
|
||||
|
@ -238,7 +238,7 @@ class BookSortTest extends TestCase
|
|||
|
||||
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'));
|
||||
|
||||
|
@ -247,7 +247,7 @@ class BookSortTest extends TestCase
|
|||
|
||||
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;
|
||||
|
@ -261,7 +261,7 @@ class BookSortTest extends TestCase
|
|||
$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'));
|
||||
|
|
|
@ -4,26 +4,26 @@ namespace Sorting;
|
|||
|
||||
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]) {
|
||||
|
@ -42,63 +42,63 @@ class SortSetTest extends TestCase
|
|||
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();
|
||||
|
@ -107,48 +107,48 @@ class SortSetTest extends TestCase
|
|||
|
||||
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", [
|
||||
|
@ -168,8 +168,8 @@ class SortSetTest extends TestCase
|
|||
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);
|
||||
|
Loading…
Reference in a new issue