mirror of
https://github.com/BookStackApp/BookStack.git
synced 2025-07-31 15:24:31 +03:00
Input WYSIWYG: Added reference store & fetch handling
For book, shelves and chapters. Made much of the existing handling generic to entity types. Added new MixedEntityListLoader to help load lists somewhat efficiently. Only manually tested so far.
This commit is contained in:
@ -138,7 +138,7 @@ class BookController extends Controller
|
||||
'bookParentShelves' => $bookParentShelves,
|
||||
'watchOptions' => new UserEntityWatchOptions(user(), $book),
|
||||
'activity' => $activities->entityActivity($book, 20, 1),
|
||||
'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($book),
|
||||
'referenceCount' => $this->referenceFetcher->getReferenceCountToEntity($book),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -125,7 +125,7 @@ class BookshelfController extends Controller
|
||||
'view' => $view,
|
||||
'activity' => $activities->entityActivity($shelf, 20, 1),
|
||||
'listOptions' => $listOptions,
|
||||
'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($shelf),
|
||||
'referenceCount' => $this->referenceFetcher->getReferenceCountToEntity($shelf),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -86,7 +86,7 @@ class ChapterController extends Controller
|
||||
'pages' => $pages,
|
||||
'next' => $nextPreviousLocator->getNext(),
|
||||
'previous' => $nextPreviousLocator->getPrevious(),
|
||||
'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($chapter),
|
||||
'referenceCount' => $this->referenceFetcher->getReferenceCountToEntity($chapter),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -155,7 +155,7 @@ class PageController extends Controller
|
||||
'watchOptions' => new UserEntityWatchOptions(user(), $page),
|
||||
'next' => $nextPreviousLocator->getNext(),
|
||||
'previous' => $nextPreviousLocator->getPrevious(),
|
||||
'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($page),
|
||||
'referenceCount' => $this->referenceFetcher->getReferenceCountToEntity($page),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ class Book extends Entity implements HasCoverImage
|
||||
use HasFactory;
|
||||
use HasHtmlDescription;
|
||||
|
||||
public $searchFactor = 1.2;
|
||||
public float $searchFactor = 1.2;
|
||||
|
||||
protected $fillable = ['name'];
|
||||
protected $hidden = ['pivot', 'image_id', 'deleted_at'];
|
||||
|
@ -15,7 +15,7 @@ class Bookshelf extends Entity implements HasCoverImage
|
||||
|
||||
protected $table = 'bookshelves';
|
||||
|
||||
public $searchFactor = 1.2;
|
||||
public float $searchFactor = 1.2;
|
||||
|
||||
protected $fillable = ['name', 'description', 'image_id'];
|
||||
|
||||
|
@ -17,7 +17,7 @@ class Chapter extends BookChild
|
||||
use HasFactory;
|
||||
use HasHtmlDescription;
|
||||
|
||||
public $searchFactor = 1.2;
|
||||
public float $searchFactor = 1.2;
|
||||
|
||||
protected $fillable = ['name', 'description', 'priority'];
|
||||
protected $hidden = ['pivot', 'deleted_at'];
|
||||
|
@ -57,12 +57,17 @@ abstract class Entity extends Model implements Sluggable, Favouritable, Viewable
|
||||
/**
|
||||
* @var string - Name of property where the main text content is found
|
||||
*/
|
||||
public $textField = 'description';
|
||||
public string $textField = 'description';
|
||||
|
||||
/**
|
||||
* @var string - Name of the property where the main HTML content is found
|
||||
*/
|
||||
public string $htmlField = 'description_html';
|
||||
|
||||
/**
|
||||
* @var float - Multiplier for search indexing.
|
||||
*/
|
||||
public $searchFactor = 1.0;
|
||||
public float $searchFactor = 1.0;
|
||||
|
||||
/**
|
||||
* Get the entities that are visible to the current user.
|
||||
|
@ -37,7 +37,8 @@ class Page extends BookChild
|
||||
|
||||
protected $fillable = ['name', 'priority'];
|
||||
|
||||
public $textField = 'text';
|
||||
public string $textField = 'text';
|
||||
public string $htmlField = 'html';
|
||||
|
||||
protected $hidden = ['html', 'markdown', 'text', 'pivot', 'deleted_at'];
|
||||
|
||||
|
@ -7,6 +7,7 @@ use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Entities\Models\HasCoverImage;
|
||||
use BookStack\Entities\Models\HasHtmlDescription;
|
||||
use BookStack\Exceptions\ImageUploadException;
|
||||
use BookStack\References\ReferenceStore;
|
||||
use BookStack\References\ReferenceUpdater;
|
||||
use BookStack\Uploads\ImageRepo;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
@ -16,7 +17,8 @@ class BaseRepo
|
||||
public function __construct(
|
||||
protected TagRepo $tagRepo,
|
||||
protected ImageRepo $imageRepo,
|
||||
protected ReferenceUpdater $referenceUpdater
|
||||
protected ReferenceUpdater $referenceUpdater,
|
||||
protected ReferenceStore $referenceStore,
|
||||
) {
|
||||
}
|
||||
|
||||
@ -42,6 +44,7 @@ class BaseRepo
|
||||
$entity->refresh();
|
||||
$entity->rebuildPermissions();
|
||||
$entity->indexForSearch();
|
||||
$this->referenceStore->updateForEntity($entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -68,6 +71,7 @@ class BaseRepo
|
||||
|
||||
$entity->rebuildPermissions();
|
||||
$entity->indexForSearch();
|
||||
$this->referenceStore->updateForEntity($entity);
|
||||
|
||||
if ($oldUrl !== $entity->getUrl()) {
|
||||
$this->referenceUpdater->updateEntityPageReferences($entity, $oldUrl);
|
||||
|
@ -162,7 +162,6 @@ class PageRepo
|
||||
$this->baseRepo->update($draft, $input);
|
||||
|
||||
$this->revisionRepo->storeNewForPage($draft, trans('entities.pages_initial_revision'));
|
||||
$this->referenceStore->updateForPage($draft);
|
||||
$draft->refresh();
|
||||
|
||||
Activity::add(ActivityType::PAGE_CREATE, $draft);
|
||||
@ -182,7 +181,6 @@ class PageRepo
|
||||
|
||||
$this->updateTemplateStatusAndContentFromInput($page, $input);
|
||||
$this->baseRepo->update($page, $input);
|
||||
$this->referenceStore->updateForPage($page);
|
||||
|
||||
// Update with new details
|
||||
$page->revision_count++;
|
||||
@ -301,7 +299,7 @@ class PageRepo
|
||||
$page->refreshSlug();
|
||||
$page->save();
|
||||
$page->indexForSearch();
|
||||
$this->referenceStore->updateForPage($page);
|
||||
$this->referenceStore->updateForEntity($page);
|
||||
|
||||
$summary = trans('entities.pages_revision_restored_from', ['id' => strval($revisionId), 'summary' => $revision->summary]);
|
||||
$this->revisionRepo->storeNewForPage($page, $summary);
|
||||
|
103
app/Entities/Tools/MixedEntityListLoader.php
Normal file
103
app/Entities/Tools/MixedEntityListLoader.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace BookStack\Entities\Tools;
|
||||
|
||||
use BookStack\App\Model;
|
||||
use BookStack\Entities\EntityProvider;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
|
||||
class MixedEntityListLoader
|
||||
{
|
||||
protected array $listAttributes = [
|
||||
'page' => ['id', 'name', 'slug', 'book_id', 'chapter_id', 'text', 'draft'],
|
||||
'chapter' => ['id', 'name', 'slug', 'book_id', 'description'],
|
||||
'book' => ['id', 'name', 'slug', 'description'],
|
||||
'bookshelf' => ['id', 'name', 'slug', 'description'],
|
||||
];
|
||||
|
||||
public function __construct(
|
||||
protected EntityProvider $entityProvider
|
||||
) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Efficiently load in entities for listing onto the given list
|
||||
* where entities are set as a relation via the given name.
|
||||
* This will look for a model id and type via 'name_id' and 'name_type'.
|
||||
* @param Model[] $relations
|
||||
*/
|
||||
public function loadIntoRelations(array $relations, string $relationName): void
|
||||
{
|
||||
$idsByType = [];
|
||||
foreach ($relations as $relation) {
|
||||
$type = $relation->getAttribute($relationName . '_type');
|
||||
$id = $relation->getAttribute($relationName . '_id');
|
||||
|
||||
if (!isset($idsByType[$type])) {
|
||||
$idsByType[$type] = [];
|
||||
}
|
||||
|
||||
$idsByType[$type][] = $id;
|
||||
}
|
||||
|
||||
$modelMap = $this->idsByTypeToModelMap($idsByType);
|
||||
|
||||
foreach ($relations as $relation) {
|
||||
$type = $relation->getAttribute($relationName . '_type');
|
||||
$id = $relation->getAttribute($relationName . '_id');
|
||||
$related = $modelMap[$type][strval($id)] ?? null;
|
||||
if ($related) {
|
||||
$relation->setRelation($relationName, $related);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, int[]> $idsByType
|
||||
* @return array<string, array<int, Model>>
|
||||
*/
|
||||
protected function idsByTypeToModelMap(array $idsByType): array
|
||||
{
|
||||
$modelMap = [];
|
||||
|
||||
foreach ($idsByType as $type => $ids) {
|
||||
if (!isset($this->listAttributes[$type])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$instance = $this->entityProvider->get($type);
|
||||
$models = $instance->newQuery()
|
||||
->select($this->listAttributes[$type])
|
||||
->scopes('visible')
|
||||
->whereIn('id', $ids)
|
||||
->with($this->getRelationsToEagerLoad($type))
|
||||
->get();
|
||||
|
||||
if (count($models) > 0) {
|
||||
$modelMap[$type] = [];
|
||||
}
|
||||
|
||||
foreach ($models as $model) {
|
||||
$modelMap[$type][strval($model->id)] = $model;
|
||||
}
|
||||
}
|
||||
|
||||
return $modelMap;
|
||||
}
|
||||
|
||||
protected function getRelationsToEagerLoad(string $type): array
|
||||
{
|
||||
$toLoad = [];
|
||||
$loadVisible = fn (Relation $query) => $query->scopes('visible');
|
||||
|
||||
if ($type === 'chapter' || $type === 'page') {
|
||||
$toLoad['book'] = $loadVisible;
|
||||
}
|
||||
|
||||
if ($type === 'page') {
|
||||
$toLoad['chapter'] = $loadVisible;
|
||||
}
|
||||
|
||||
return $toLoad;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user