Sorting: Added book autosort logic
This commit is contained in:
parent
7093daa49d
commit
c13ce18837
|
@ -4,6 +4,7 @@ namespace BookStack\Entities\Repos;
|
||||||
|
|
||||||
use BookStack\Activity\TagRepo;
|
use BookStack\Activity\TagRepo;
|
||||||
use BookStack\Entities\Models\Book;
|
use BookStack\Entities\Models\Book;
|
||||||
|
use BookStack\Entities\Models\BookChild;
|
||||||
use BookStack\Entities\Models\Chapter;
|
use BookStack\Entities\Models\Chapter;
|
||||||
use BookStack\Entities\Models\Entity;
|
use BookStack\Entities\Models\Entity;
|
||||||
use BookStack\Entities\Models\HasCoverImage;
|
use BookStack\Entities\Models\HasCoverImage;
|
||||||
|
@ -12,6 +13,7 @@ use BookStack\Entities\Queries\PageQueries;
|
||||||
use BookStack\Exceptions\ImageUploadException;
|
use BookStack\Exceptions\ImageUploadException;
|
||||||
use BookStack\References\ReferenceStore;
|
use BookStack\References\ReferenceStore;
|
||||||
use BookStack\References\ReferenceUpdater;
|
use BookStack\References\ReferenceUpdater;
|
||||||
|
use BookStack\Sorting\BookSorter;
|
||||||
use BookStack\Uploads\ImageRepo;
|
use BookStack\Uploads\ImageRepo;
|
||||||
use BookStack\Util\HtmlDescriptionFilter;
|
use BookStack\Util\HtmlDescriptionFilter;
|
||||||
use Illuminate\Http\UploadedFile;
|
use Illuminate\Http\UploadedFile;
|
||||||
|
@ -24,6 +26,7 @@ class BaseRepo
|
||||||
protected ReferenceUpdater $referenceUpdater,
|
protected ReferenceUpdater $referenceUpdater,
|
||||||
protected ReferenceStore $referenceStore,
|
protected ReferenceStore $referenceStore,
|
||||||
protected PageQueries $pageQueries,
|
protected PageQueries $pageQueries,
|
||||||
|
protected BookSorter $bookSorter,
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -134,6 +137,18 @@ class BaseRepo
|
||||||
$entity->save();
|
$entity->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort the parent of the given entity, if any auto sort actions are set for it.
|
||||||
|
* Typical ran during create/update/insert events.
|
||||||
|
*/
|
||||||
|
public function sortParent(Entity $entity): void
|
||||||
|
{
|
||||||
|
if ($entity instanceof BookChild) {
|
||||||
|
$book = $entity->book;
|
||||||
|
$this->bookSorter->runBookAutoSort($book);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected function updateDescription(Entity $entity, array $input): void
|
protected function updateDescription(Entity $entity, array $input): void
|
||||||
{
|
{
|
||||||
if (!in_array(HasHtmlDescription::class, class_uses($entity))) {
|
if (!in_array(HasHtmlDescription::class, class_uses($entity))) {
|
||||||
|
|
|
@ -34,6 +34,8 @@ class ChapterRepo
|
||||||
$this->baseRepo->updateDefaultTemplate($chapter, intval($input['default_template_id'] ?? null));
|
$this->baseRepo->updateDefaultTemplate($chapter, intval($input['default_template_id'] ?? null));
|
||||||
Activity::add(ActivityType::CHAPTER_CREATE, $chapter);
|
Activity::add(ActivityType::CHAPTER_CREATE, $chapter);
|
||||||
|
|
||||||
|
$this->baseRepo->sortParent($chapter);
|
||||||
|
|
||||||
return $chapter;
|
return $chapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,6 +52,8 @@ class ChapterRepo
|
||||||
|
|
||||||
Activity::add(ActivityType::CHAPTER_UPDATE, $chapter);
|
Activity::add(ActivityType::CHAPTER_UPDATE, $chapter);
|
||||||
|
|
||||||
|
$this->baseRepo->sortParent($chapter);
|
||||||
|
|
||||||
return $chapter;
|
return $chapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -88,6 +92,8 @@ class ChapterRepo
|
||||||
$chapter->rebuildPermissions();
|
$chapter->rebuildPermissions();
|
||||||
Activity::add(ActivityType::CHAPTER_MOVE, $chapter);
|
Activity::add(ActivityType::CHAPTER_MOVE, $chapter);
|
||||||
|
|
||||||
|
$this->baseRepo->sortParent($chapter);
|
||||||
|
|
||||||
return $parent;
|
return $parent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -83,6 +83,7 @@ class PageRepo
|
||||||
$draft->refresh();
|
$draft->refresh();
|
||||||
|
|
||||||
Activity::add(ActivityType::PAGE_CREATE, $draft);
|
Activity::add(ActivityType::PAGE_CREATE, $draft);
|
||||||
|
$this->baseRepo->sortParent($draft);
|
||||||
|
|
||||||
return $draft;
|
return $draft;
|
||||||
}
|
}
|
||||||
|
@ -128,6 +129,7 @@ class PageRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
Activity::add(ActivityType::PAGE_UPDATE, $page);
|
Activity::add(ActivityType::PAGE_UPDATE, $page);
|
||||||
|
$this->baseRepo->sortParent($page);
|
||||||
|
|
||||||
return $page;
|
return $page;
|
||||||
}
|
}
|
||||||
|
@ -243,6 +245,8 @@ class PageRepo
|
||||||
Activity::add(ActivityType::PAGE_RESTORE, $page);
|
Activity::add(ActivityType::PAGE_RESTORE, $page);
|
||||||
Activity::add(ActivityType::REVISION_RESTORE, $revision);
|
Activity::add(ActivityType::REVISION_RESTORE, $revision);
|
||||||
|
|
||||||
|
$this->baseRepo->sortParent($page);
|
||||||
|
|
||||||
return $page;
|
return $page;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,6 +276,8 @@ class PageRepo
|
||||||
|
|
||||||
Activity::add(ActivityType::PAGE_MOVE, $page);
|
Activity::add(ActivityType::PAGE_MOVE, $page);
|
||||||
|
|
||||||
|
$this->baseRepo->sortParent($page);
|
||||||
|
|
||||||
return $parent;
|
return $parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,54 @@ class BookSorter
|
||||||
) {
|
) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs the auto-sort for a book if the book has a sort set applied to it.
|
||||||
|
* This does not consider permissions since the sort operations are centrally
|
||||||
|
* managed by admins so considered permitted if existing and assigned.
|
||||||
|
*/
|
||||||
|
public function runBookAutoSort(Book $book): void
|
||||||
|
{
|
||||||
|
$set = $book->sortSet;
|
||||||
|
if (!$set) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sortFunctions = array_map(function (SortSetOperation $op) {
|
||||||
|
return $op->getSortFunction();
|
||||||
|
}, $set->getOperations());
|
||||||
|
|
||||||
|
$chapters = $book->chapters()
|
||||||
|
->with('pages:id,name,priority,created_at,updated_at')
|
||||||
|
->get(['id', 'name', 'priority', 'created_at', 'updated_at']);
|
||||||
|
|
||||||
|
/** @var (Chapter|Book)[] $topItems */
|
||||||
|
$topItems = [
|
||||||
|
...$book->directPages()->get(['id', 'name', 'priority', 'created_at', 'updated_at']),
|
||||||
|
...$chapters,
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($sortFunctions as $sortFunction) {
|
||||||
|
usort($topItems, $sortFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($topItems as $index => $topItem) {
|
||||||
|
$topItem->priority = $index + 1;
|
||||||
|
$topItem->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($chapters as $chapter) {
|
||||||
|
$pages = $chapter->pages->all();
|
||||||
|
foreach ($sortFunctions as $sortFunction) {
|
||||||
|
usort($pages, $sortFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($pages as $index => $page) {
|
||||||
|
$page->priority = $index + 1;
|
||||||
|
$page->save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sort the books content using the given sort map.
|
* Sort the books content using the given sort map.
|
||||||
|
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
namespace BookStack\Sorting;
|
namespace BookStack\Sorting;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
enum SortSetOperation: string
|
enum SortSetOperation: string
|
||||||
{
|
{
|
||||||
case NameAsc = 'name_asc';
|
case NameAsc = 'name_asc';
|
||||||
|
@ -33,6 +36,12 @@ enum SortSetOperation: string
|
||||||
return trim($label);
|
return trim($label);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getSortFunction(): callable
|
||||||
|
{
|
||||||
|
$camelValue = Str::camel($this->value);
|
||||||
|
return SortSetOperationComparisons::$camelValue(...);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return SortSetOperation[]
|
* @return SortSetOperation[]
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace BookStack\Sorting;
|
||||||
|
|
||||||
|
use BookStack\Entities\Models\Chapter;
|
||||||
|
use BookStack\Entities\Models\Entity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sort comparison function for each of the possible SortSetOperation values.
|
||||||
|
* Method names should be camelCase names for the SortSetOperation enum value.
|
||||||
|
* TODO - Test to cover each SortSetOperation enum value is covered.
|
||||||
|
*/
|
||||||
|
class SortSetOperationComparisons
|
||||||
|
{
|
||||||
|
public static function nameAsc(Entity $a, Entity $b): int
|
||||||
|
{
|
||||||
|
return $a->name <=> $b->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function nameDesc(Entity $a, Entity $b): int
|
||||||
|
{
|
||||||
|
return $b->name <=> $a->name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function nameNumericAsc(Entity $a, Entity $b): int
|
||||||
|
{
|
||||||
|
$numRegex = '/^\d+(\.\d+)?/';
|
||||||
|
$aMatches = [];
|
||||||
|
$bMatches = [];
|
||||||
|
preg_match($numRegex, $a, $aMatches);
|
||||||
|
preg_match($numRegex, $b, $bMatches);
|
||||||
|
return ($aMatches[0] ?? 0) <=> ($bMatches[0] ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function nameNumericDesc(Entity $a, Entity $b): int
|
||||||
|
{
|
||||||
|
return -(static::nameNumericAsc($a, $b));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createdDateAsc(Entity $a, Entity $b): int
|
||||||
|
{
|
||||||
|
return $a->created_at->unix() <=> $b->created_at->unix();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createdDateDesc(Entity $a, Entity $b): int
|
||||||
|
{
|
||||||
|
return $b->created_at->unix() <=> $a->created_at->unix();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function updatedDateAsc(Entity $a, Entity $b): int
|
||||||
|
{
|
||||||
|
return $a->updated_at->unix() <=> $b->updated_at->unix();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function updatedDateDesc(Entity $a, Entity $b): int
|
||||||
|
{
|
||||||
|
return $b->updated_at->unix() <=> $a->updated_at->unix();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function chaptersFirst(Entity $a, Entity $b): int
|
||||||
|
{
|
||||||
|
return ($b instanceof Chapter ? 1 : 0) - (($a instanceof Chapter) ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function chaptersLast(Entity $a, Entity $b): int
|
||||||
|
{
|
||||||
|
return ($a instanceof Chapter ? 1 : 0) - (($b instanceof Chapter) ? 1 : 0);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue