1
0
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:
Dan Brown
2023-12-18 16:23:40 +00:00
parent c622b785a9
commit 307fae39c4
21 changed files with 219 additions and 97 deletions

View File

@ -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),
]);
}

View File

@ -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),
]);
}

View File

@ -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),
]);
}

View File

@ -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),
]);
}

View File

@ -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'];

View File

@ -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'];

View File

@ -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'];

View File

@ -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.

View File

@ -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'];

View File

@ -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);

View File

@ -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);

View 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;
}
}