From 12290ae1aafcfed944ef3dc5c332ab35bc3d633c Mon Sep 17 00:00:00 2001 From: Brij Mandaliya Date: Thu, 24 Oct 2024 19:02:09 +0530 Subject: [PATCH 01/10] Buid: Cover image for pages and also add grid view for list of pages in chapter show page with test case and update create test case for page and add two test case one for update cover image for page and another for reset cover image for page --- app/App/helpers.php | 19 +++++ app/Config/setting-defaults.php | 1 + .../Controllers/ChapterController.php | 2 + app/Entities/Controllers/PageController.php | 6 ++ app/Entities/Models/Page.php | 29 ++++++- app/Entities/Queries/PageQueries.php | 9 ++- app/Entities/Repos/PageRepo.php | 8 ++ .../Controllers/UserPreferencesController.php | 2 +- ...0_24_074404_add_image_id_in_page_table.php | 28 +++++++ resources/sass/_colors.scss | 3 + resources/views/chapters/show.blade.php | 20 +++-- resources/views/entities/grid-item.blade.php | 2 +- resources/views/pages/edit.blade.php | 2 +- .../pages/parts/editor-toolbox.blade.php | 15 ++++ .../views/pages/parts/list-item.blade.php | 15 +++- tests/Entity/PageTest.php | 80 +++++++++++++++++++ 16 files changed, 226 insertions(+), 15 deletions(-) create mode 100755 database/migrations/2024_10_24_074404_add_image_id_in_page_table.php diff --git a/app/App/helpers.php b/app/App/helpers.php index af6dbcfc397..c42e0cb5e86 100644 --- a/app/App/helpers.php +++ b/app/App/helpers.php @@ -128,3 +128,22 @@ function sortUrl(string $path, array $data, array $overrideData = []): string return url($path . '?' . implode('&', $queryStringSections)); } + +function getCover($entity,$type) +{ + if(!in_array($type,['page','book','bookshelf'])) + { + return ''; + } + + if($type === 'page') + { + return $entity->getPageCover(); + } + + if($type === 'book' || $type === 'bookshelf') + { + return $entity->getBookCover(); + } + +} diff --git a/app/Config/setting-defaults.php b/app/Config/setting-defaults.php index 88c4612ca61..b72a05ecba4 100644 --- a/app/Config/setting-defaults.php +++ b/app/Config/setting-defaults.php @@ -41,6 +41,7 @@ 'bookshelves_view_type' => env('APP_VIEWS_BOOKSHELVES', 'grid'), 'bookshelf_view_type' => env('APP_VIEWS_BOOKSHELF', 'grid'), 'books_view_type' => env('APP_VIEWS_BOOKS', 'grid'), + 'pages_view_type' => env('APP_VIEWS_BOOKS', 'grid'), ], ]; diff --git a/app/Entities/Controllers/ChapterController.php b/app/Entities/Controllers/ChapterController.php index 4274589e260..52b989fa924 100644 --- a/app/Entities/Controllers/ChapterController.php +++ b/app/Entities/Controllers/ChapterController.php @@ -75,6 +75,7 @@ public function store(Request $request, string $bookSlug) */ public function show(string $bookSlug, string $chapterSlug) { + $view = setting()->getForCurrentUser(key: 'pages_view_type'); $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); $this->checkOwnablePermission('chapter-view', $chapter); @@ -96,6 +97,7 @@ public function show(string $bookSlug, string $chapterSlug) 'next' => $nextPreviousLocator->getNext(), 'previous' => $nextPreviousLocator->getPrevious(), 'referenceCount' => $this->referenceFetcher->getReferenceCountToEntity($chapter), + 'view' => $view, ]); } diff --git a/app/Entities/Controllers/PageController.php b/app/Entities/Controllers/PageController.php index eab53bb2510..307ff43d3f2 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -222,6 +222,12 @@ public function update(Request $request, string $bookSlug, string $pageSlug) $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkOwnablePermission('page-update', $page); + if ($request->has('image_reset')) { + $request['image'] = null; + } elseif (array_key_exists('image', $request->all()) && is_null($request['image'])) { + unset($request['image']); + } + $this->pageRepo->update($page, $request->all()); return redirect($page->getUrl()); diff --git a/app/Entities/Models/Page.php b/app/Entities/Models/Page.php index 499ef4d7288..ceb428b5f2d 100644 --- a/app/Entities/Models/Page.php +++ b/app/Entities/Models/Page.php @@ -2,10 +2,12 @@ namespace BookStack\Entities\Models; +use BookStack\Uploads\Image; use BookStack\Entities\Tools\PageContent; use BookStack\Entities\Tools\PageEditorType; use BookStack\Permissions\PermissionApplicator; use BookStack\Uploads\Attachment; +use Exception; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -24,12 +26,13 @@ * @property bool $draft * @property int $revision_count * @property string $editor + * @property Image|null $cover * @property Chapter $chapter * @property Collection $attachments * @property Collection $revisions * @property PageRevision $currentRevision */ -class Page extends BookChild +class Page extends BookChild implements HasCoverImage { use HasFactory; @@ -143,4 +146,28 @@ public function forJsonDisplay(): self return $refreshed; } + + public function getPageCover(int $width = 440, int $height = 250): string + { + $default = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; + if (!$this->image_id || !$this->cover) { + return $default; + } + + try { + return $this->cover->getThumb($width, $height, false) ?? $default; + } catch (Exception $err) { + return $default; + } + } + + public function cover(): BelongsTo + { + return $this->belongsTo(Image::class, 'image_id'); + } + + public function coverImageTypeKey(): string + { + return 'cover_page'; + } } diff --git a/app/Entities/Queries/PageQueries.php b/app/Entities/Queries/PageQueries.php index 06298f470b9..209b7d97be1 100644 --- a/app/Entities/Queries/PageQueries.php +++ b/app/Entities/Queries/PageQueries.php @@ -15,7 +15,7 @@ class PageQueries implements ProvidesEntityQueries ]; protected static array $listAttributes = [ 'name', 'id', 'slug', 'book_id', 'chapter_id', 'draft', - 'template', 'text', 'created_at', 'updated_at', 'priority', 'owned_by', + 'template', 'text', 'created_at', 'updated_at', 'priority', 'owned_by','image_id', ]; public function start(): Builder @@ -75,7 +75,7 @@ public function visibleForList(): Builder public function visibleForChapterList(int $chapterId): Builder { - return $this->visibleForList() + return $this->visibleForListWithCover() ->where('chapter_id', '=', $chapterId) ->orderBy('draft', 'desc') ->orderBy('priority', 'asc'); @@ -109,4 +109,9 @@ protected function mergeBookSlugForSelect(array $columns): array ->whereColumn('books.id', '=', 'pages.book_id'); }]); } + + public function visibleForListWithCover(): Builder + { + return $this->visibleForList()->with('cover'); + } } diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 1bc15392cec..ea4a75cee1f 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -78,6 +78,10 @@ public function publishDraft(Page $draft, array $input): Page $this->updateTemplateStatusAndContentFromInput($draft, $input); $this->baseRepo->update($draft, $input); + if (array_key_exists('image', $input)) { + $this->baseRepo->updateCoverImage($draft, $input['image'], $input['image'] === null); + } + $summary = trim($input['summary'] ?? '') ?: trans('entities.pages_initial_revision'); $this->revisionRepo->storeNewForPage($draft, $summary); $draft->refresh(); @@ -116,6 +120,10 @@ public function update(Page $page, array $input): Page $this->revisionRepo->storeNewForPage($page, $summary); } + if (array_key_exists('image', $input)) { + $this->baseRepo->updateCoverImage($page, $input['image'], $input['image'] === null); + } + Activity::add(ActivityType::PAGE_UPDATE, $page); return $page; diff --git a/app/Users/Controllers/UserPreferencesController.php b/app/Users/Controllers/UserPreferencesController.php index 0bed2d22a43..ebbf35283ff 100644 --- a/app/Users/Controllers/UserPreferencesController.php +++ b/app/Users/Controllers/UserPreferencesController.php @@ -18,7 +18,7 @@ public function __construct( */ public function changeView(Request $request, string $type) { - $valueViewTypes = ['books', 'bookshelves', 'bookshelf']; + $valueViewTypes = ['books', 'bookshelves', 'bookshelf','pages']; if (!in_array($type, $valueViewTypes)) { return $this->redirectToRequest($request); } diff --git a/database/migrations/2024_10_24_074404_add_image_id_in_page_table.php b/database/migrations/2024_10_24_074404_add_image_id_in_page_table.php new file mode 100755 index 00000000000..8b252034ead --- /dev/null +++ b/database/migrations/2024_10_24_074404_add_image_id_in_page_table.php @@ -0,0 +1,28 @@ +integer('image_id')->after('priority')->nullable()->default(null); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('page', function (Blueprint $table) { + // + }); + } +}; diff --git a/resources/sass/_colors.scss b/resources/sass/_colors.scss index c77c1d8b388..6338e67a044 100644 --- a/resources/sass/_colors.scss +++ b/resources/sass/_colors.scss @@ -103,3 +103,6 @@ .bg-bookshelf { background-color: var(--color-bookshelf); } +.bg-page { + background-color: var(--color-page); +} diff --git a/resources/views/chapters/show.blade.php b/resources/views/chapters/show.blade.php index 45e43ad96a9..a1a3dd841de 100644 --- a/resources/views/chapters/show.blade.php +++ b/resources/views/chapters/show.blade.php @@ -26,11 +26,19 @@
{!! $chapter->descriptionHtml() !!}
@if(count($pages) > 0) -
- @foreach($pages as $page) - @include('pages.parts.list-item', ['page' => $page]) - @endforeach -
+ @if($view === 'list') +
+ @foreach($pages as $page) + @include('pages.parts.list-item', ['page' => $page]) + @endforeach +
+ @else +
+ @foreach($pages as $page) + @include('entities.grid-item', ['entity' => $page]) + @endforeach +
+ @endif @else

@@ -107,6 +115,8 @@
{{ trans('common.actions') }}
diff --git a/resources/views/pages/parts/list-item.blade.php b/resources/views/pages/parts/list-item.blade.php index 5707a9c6619..5a925da2b0d 100644 --- a/resources/views/pages/parts/list-item.blade.php +++ b/resources/views/pages/parts/list-item.blade.php @@ -1,5 +1,12 @@ -@component('entities.list-item-basic', ['entity' => $page]) -
-

{{ $page->getExcerpt() }}

+
+ @icon('page') +
+ @icon('page')
-@endcomponent \ No newline at end of file +
+

{{ $page->name }}

+
+

{{ $page->getExcerpt() }}

+
+
+
\ No newline at end of file diff --git a/tests/Entity/PageTest.php b/tests/Entity/PageTest.php index b96d455eb25..27f2a2ee88a 100644 --- a/tests/Entity/PageTest.php +++ b/tests/Entity/PageTest.php @@ -4,6 +4,7 @@ use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Page; +use BookStack\Entities\Repos\BaseRepo; use Carbon\Carbon; use Tests\TestCase; @@ -27,6 +28,15 @@ public function test_create() ->first(); $resp->assertRedirect($draftPage->getUrl()); + $baseRepo = $this->app->make(BaseRepo::class); + + $coverImageFile = $this->files->uploadedImage('page_cover.png'); + + $baseRepo->updateCoverImage($draftPage, $coverImageFile); + + $this->assertNotNull($draftPage->cover); + $this->assertEquals('page_cover.png', $draftPage->cover->name); + $resp = $this->get($draftPage->getUrl()); $this->withHtml($resp)->assertElementContains('form[action="' . $draftPage->getUrl() . '"][method="POST"]', 'Save Page'); @@ -356,4 +366,74 @@ public function test_recently_updated_pages_on_home() $resp = $this->get('/'); $this->withHtml($resp)->assertElementContains('#recently-updated-pages', $page->name); } + + public function test_page_cover_image_reset() + { + $page = $this->entities->page(); + + $baseRepo = $this->app->make(BaseRepo::class); + + $coverImageFile = $this->files->uploadedImage('page_cover.png'); + + $baseRepo->updateCoverImage($page, $coverImageFile); + + $this->assertNotNull($page->cover); + $this->assertEquals('page_cover.png', $page->cover->name); + + $page = Page::findOrFail($page->id); + + // Third argument specify for reset image is true or false + $baseRepo->updateCoverImage($page, null,true); + + $this->assertNull($page->cover); + } + + public function test_page_cover_image_update() + { + $page = $this->entities->page(); + + $baseRepo = $this->app->make(BaseRepo::class); + + $coverImageFile = $this->files->uploadedImage('page_cover.png'); + + $baseRepo->updateCoverImage($page, $coverImageFile); + + $this->assertNotNull($page->cover); + $this->assertEquals('page_cover.png', $page->cover->name); + + $coverImageFile = $this->files->uploadedImage('updated_page_cover.png'); + + $baseRepo->updateCoverImage($page, $coverImageFile); + + $this->assertNotNull($page->cover); + $this->assertEquals('updated_page_cover.png', $page->cover->name); + } + + public function test_pages_in_chapter_view_shows_view_toggle_option() + { + $editor = $this->users->editor(); + setting()->putUser($editor, 'pages_view_type', 'list'); + + + $book = $this->entities->book(); + + $chapter = $this->entities->chapter()->where('book_id',$book->id)->get(); + + $resp = $this->actingAs($editor)->get("/books/{$book->slug}/chapter/{$chapter->first()->slug}"); + + $this->withHtml($resp)->assertElementContains('form[action$="/preferences/change-view/pages"]', 'Grid View'); + $this->withHtml($resp)->assertElementExists('button[name="view"][value="grid"]'); + + $resp = $this->patch("/preferences/change-view/pages", ['view' => 'grid']); + $resp->assertRedirect(); + $this->assertEquals('grid', setting()->getUser($editor, 'pages_view_type')); + + $resp = $this->actingAs($editor)->get("/books/{$book->slug}/chapter/{$chapter->first()->slug}"); + $this->withHtml($resp)->assertElementContains('form[action$="/preferences/change-view/pages"]', 'List View'); + $this->withHtml($resp)->assertElementExists('button[name="view"][value="list"]'); + + $resp = $this->patch("/preferences/change-view/pages", ['view_type' => 'list']); + $resp->assertRedirect(); + $this->assertEquals('list', setting()->getUser($editor, 'pages_view_type')); + } } From abd98e114d54cc50fcff68fbd4f69e79bf533ba9 Mon Sep 17 00:00:00 2001 From: Brij Mandaliya Date: Thu, 24 Oct 2024 19:16:59 +0530 Subject: [PATCH 02/10] fix: lint php --- app/App/helpers.php | 14 +++++--------- tests/Entity/PageTest.php | 6 +++--- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/app/App/helpers.php b/app/App/helpers.php index c42e0cb5e86..617cc49eba0 100644 --- a/app/App/helpers.php +++ b/app/App/helpers.php @@ -129,21 +129,17 @@ function sortUrl(string $path, array $data, array $overrideData = []): string return url($path . '?' . implode('&', $queryStringSections)); } -function getCover($entity,$type) +function getCover($entity, $type) { - if(!in_array($type,['page','book','bookshelf'])) - { + if (!in_array($type, ['page', 'book', 'bookshelf'])) { return ''; } - - if($type === 'page') - { + + if ($type === 'page') { return $entity->getPageCover(); } - if($type === 'book' || $type === 'bookshelf') - { + if ($type === 'book' || $type === 'bookshelf') { return $entity->getBookCover(); } - } diff --git a/tests/Entity/PageTest.php b/tests/Entity/PageTest.php index 27f2a2ee88a..a1fe8c2be5b 100644 --- a/tests/Entity/PageTest.php +++ b/tests/Entity/PageTest.php @@ -383,7 +383,7 @@ public function test_page_cover_image_reset() $page = Page::findOrFail($page->id); // Third argument specify for reset image is true or false - $baseRepo->updateCoverImage($page, null,true); + $baseRepo->updateCoverImage($page, null, true); $this->assertNull($page->cover); } @@ -417,8 +417,8 @@ public function test_pages_in_chapter_view_shows_view_toggle_option() $book = $this->entities->book(); - $chapter = $this->entities->chapter()->where('book_id',$book->id)->get(); - + $chapter = $this->entities->chapter()->where('book_id', $book->id)->get(); + $resp = $this->actingAs($editor)->get("/books/{$book->slug}/chapter/{$chapter->first()->slug}"); $this->withHtml($resp)->assertElementContains('form[action$="/preferences/change-view/pages"]', 'Grid View'); From ac9945fc4d8b0416046a9dc6be3a0d05cc50d05f Mon Sep 17 00:00:00 2001 From: Brij Mandaliya Date: Tue, 29 Oct 2024 17:49:11 +0530 Subject: [PATCH 03/10] set to get default page cover image from env --- app/Config/setting-defaults.php | 1 + app/Entities/Models/Page.php | 2 +- app/Settings/SettingService.php | 5 +++++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/app/Config/setting-defaults.php b/app/Config/setting-defaults.php index b72a05ecba4..d95b99f935e 100644 --- a/app/Config/setting-defaults.php +++ b/app/Config/setting-defaults.php @@ -32,6 +32,7 @@ 'page-draft-color-dark' => '#a66ce8', 'app-custom-head' => false, 'registration-enabled' => false, + 'default_page_cover_image' => ENV('DEFAULT_PAGE_COVER_IMAGE','data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='), // User-level default settings 'user' => [ diff --git a/app/Entities/Models/Page.php b/app/Entities/Models/Page.php index ceb428b5f2d..40420016705 100644 --- a/app/Entities/Models/Page.php +++ b/app/Entities/Models/Page.php @@ -149,7 +149,7 @@ public function forJsonDisplay(): self public function getPageCover(int $width = 440, int $height = 250): string { - $default = 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='; + $default = setting()->getDefaultPageCoverImage(); if (!$this->image_id || !$this->cover) { return $default; } diff --git a/app/Settings/SettingService.php b/app/Settings/SettingService.php index 31debdaea85..36a2c5749b8 100644 --- a/app/Settings/SettingService.php +++ b/app/Settings/SettingService.php @@ -276,4 +276,9 @@ public function flushCache(): void { $this->localCache = []; } + + public function getDefaultPageCoverImage() + { + return config('setting-defaults.default_page_cover_image'); + } } From cdbc26920d170f339f5d313ec931bdf54ef8b9af Mon Sep 17 00:00:00 2001 From: Brij Mandaliya Date: Tue, 29 Oct 2024 17:50:37 +0530 Subject: [PATCH 04/10] fix: lint php --- app/Config/setting-defaults.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/Config/setting-defaults.php b/app/Config/setting-defaults.php index d95b99f935e..d6ffbbabce2 100644 --- a/app/Config/setting-defaults.php +++ b/app/Config/setting-defaults.php @@ -32,7 +32,7 @@ 'page-draft-color-dark' => '#a66ce8', 'app-custom-head' => false, 'registration-enabled' => false, - 'default_page_cover_image' => ENV('DEFAULT_PAGE_COVER_IMAGE','data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='), + 'default_page_cover_image' => ENV('DEFAULT_PAGE_COVER_IMAGE', 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='), // User-level default settings 'user' => [ From 802e57972a2b21d5584ffaea9ae487984a2cfe63 Mon Sep 17 00:00:00 2001 From: brijm-improwised Date: Tue, 14 Apr 2026 22:24:21 +0530 Subject: [PATCH 05/10] feat(pages): add image_id support and update cover handling. - Added `image_id` field to `entity_page_data` model and migration - Updated `PageQueries` to select `image_id` and reference entity_page_data columns explicitly - Modified `PageRepo` to handle cover image updates consistently for drafts and revisions - Extended `EntityCover` and `EntityHtmlDescription` to support `Page` entities - Adjusted `BookContents` queries to use entity_page_data.draft - Updated Blade views to use `coverInfo()->getUrl()` for page list items - Introduced sidebar view-toggle for pages in chapter actions - Removed `ContainerTrait` from `Page` and replaced defaultTemplate with descriptionInfo --- app/Entities/Models/EntityPageData.php | 1 + app/Entities/Models/Page.php | 10 +++-- app/Entities/Queries/PageQueries.php | 41 +++++++++++++------ app/Entities/Repos/PageRepo.php | 16 ++++---- app/Entities/Tools/BookContents.php | 2 +- app/Entities/Tools/EntityCover.php | 4 +- app/Entities/Tools/EntityHtmlDescription.php | 3 +- ...20254_add_image_id_to_entity_page_data.php | 30 ++++++++++++++ .../show-sidebar-section-actions.blade.php | 1 + .../views/pages/parts/list-item.blade.php | 2 +- 10 files changed, 81 insertions(+), 29 deletions(-) create mode 100755 database/migrations/2026_04_11_220254_add_image_id_to_entity_page_data.php diff --git a/app/Entities/Models/EntityPageData.php b/app/Entities/Models/EntityPageData.php index a98b1a9823c..0d3c62f79ce 100644 --- a/app/Entities/Models/EntityPageData.php +++ b/app/Entities/Models/EntityPageData.php @@ -21,5 +21,6 @@ class EntityPageData extends Model 'html', 'text', 'markdown', + 'image_id' ]; } diff --git a/app/Entities/Models/Page.php b/app/Entities/Models/Page.php index fdedbfe3717..d083b60e30b 100644 --- a/app/Entities/Models/Page.php +++ b/app/Entities/Models/Page.php @@ -4,6 +4,7 @@ use BookStack\Entities\Tools\EntityCover; use BookStack\Entities\Tools\EntityDefaultTemplate; +use BookStack\Entities\Tools\EntityHtmlDescription; use BookStack\Uploads\Image; use BookStack\Entities\Tools\PageContent; use BookStack\Permissions\PermissionApplicator; @@ -33,10 +34,10 @@ * @property Collection $revisions * @property PageRevision $currentRevision */ -class Page extends BookChild implements HasDescriptionInterface, HasCoverInterface, HasDefaultTemplateInterface +class Page extends BookChild implements HasDescriptionInterface, HasCoverInterface { use HasFactory; - use ContainerTrait; + // use ContainerTrait; public string $textField = 'text'; public string $htmlField = 'html'; @@ -165,10 +166,11 @@ public function getPageCover(int $width = 440, int $height = 250): string } } - public function defaultTemplate(): EntityDefaultTemplate + public function descriptionInfo(): EntityHtmlDescription { - return new EntityDefaultTemplate($this); + return new EntityHtmlDescription($this); } + public function cover(): BelongsTo { diff --git a/app/Entities/Queries/PageQueries.php b/app/Entities/Queries/PageQueries.php index 0d62bdb9d0d..7acc1b4e668 100644 --- a/app/Entities/Queries/PageQueries.php +++ b/app/Entities/Queries/PageQueries.php @@ -12,13 +12,18 @@ class PageQueries implements ProvidesEntityQueries { protected static array $contentAttributes = [ - 'name', 'id', 'slug', 'book_id', 'chapter_id', 'draft', - 'template', 'html', 'markdown', 'text', 'created_at', 'updated_at', 'priority', - 'created_by', 'updated_by', 'owned_by', + 'entities.name as name', 'entities.id as id', 'entities.slug as slug', 'entities.book_id as book_id', + 'entities.chapter_id as chapter_id', 'entity_page_data.draft as draft', + 'entity_page_data.template as template', 'entity_page_data.html as html', 'entity_page_data.markdown as markdown', + 'entity_page_data.text as text', 'entities.created_at as created_at', 'entities.updated_at as updated_at', + 'entities.priority as priority', 'entities.created_by as created_by', 'entities.updated_by as updated_by', + 'entities.owned_by as owned_by', ]; protected static array $listAttributes = [ - 'name', 'id', 'slug', 'book_id', 'chapter_id', 'draft', - 'template', 'text', 'created_at', 'updated_at', 'priority', 'owned_by','image_id', + 'entities.name as name', 'entities.id as id', 'entities.slug as slug', 'entities.book_id as book_id', + 'entities.chapter_id as chapter_id', 'entity_page_data.draft as draft', + 'entity_page_data.template as template', 'entity_page_data.text as text', 'entities.created_at as created_at', + 'entities.updated_at as updated_at', 'entities.priority as priority', 'entities.owned_by as owned_by', ]; /** @@ -79,7 +84,12 @@ public function visibleForList(): Builder { return $this->start() ->scopes('visible') - ->select($this->mergeBookSlugForSelect(static::$listAttributes)); + ->select(array_merge( + $this->mergeBookSlugForSelect(static::$listAttributes), + [ + 'entity_page_data.image_id', + ] + )); } /** @@ -93,29 +103,34 @@ public function visibleForContent(): Builder public function visibleForChapterList(int $chapterId): Builder { return $this->visibleForListWithCover() - ->where('chapter_id', '=', $chapterId) - ->orderBy('draft', 'desc') - ->orderBy('priority', 'asc'); + ->where('entities.chapter_id', '=', $chapterId) + ->orderBy('entity_page_data.draft', 'desc') + ->orderBy('entities.priority', 'asc'); } public function visibleWithContents(): Builder { return $this->start() ->scopes('visible') - ->select($this->mergeBookSlugForSelect(static::$contentAttributes)); + ->select(array_merge( + $this->mergeBookSlugForSelect(static::$contentAttributes), + [ + 'entity_page_data.image_id', + ] + )); } public function currentUserDraftsForList(): Builder { return $this->visibleForList() - ->where('draft', '=', true) - ->where('created_by', '=', user()->id); + ->where('entity_page_data.draft', '=', true) + ->where('entities.created_by', '=', user()->id); } public function visibleTemplates(bool $includeContents = false): Builder { $base = $includeContents ? $this->visibleWithContents() : $this->visibleForList(); - return $base->where('template', '=', true); + return $base->where('entity_page_data.template', '=', true); } protected function mergeBookSlugForSelect(array $columns): array diff --git a/app/Entities/Repos/PageRepo.php b/app/Entities/Repos/PageRepo.php index 73e81cf06d8..bda8b521959 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -88,9 +88,9 @@ public function publishDraft(Page $draft, array $input): Page $draft->priority = $this->getNewPriority($draft); $this->updateTemplateStatusAndContentFromInput($draft, $input); - if (array_key_exists('image', $input)) { - $this->baseRepo->updateCoverImage($draft, $input['image'], $input['image'] === null); - } + if (array_key_exists('image', $input)) { + $this->baseRepo->updateCoverImage($draft, $input['image'], $input['image'] === null); + } $draft = $this->baseRepo->update($draft, $input); $draft->rebuildPermissions(); @@ -126,17 +126,17 @@ public function update(Page $page, array $input): Page $oldName = $page->name; $oldHtml = $page->html; $oldMarkdown = $page->markdown; - + $this->updateTemplateStatusAndContentFromInput($page, $input); $page = $this->baseRepo->update($page, $input); - + // Update with new details $page->revision_count++; $page->save(); - + // Remove all update drafts for this user and page. $this->revisionRepo->deleteDraftsForCurrentUser($page); - + // Save a revision after updating $summary = trim($input['summary'] ?? ''); $htmlChanged = isset($input['html']) && $input['html'] !== $oldHtml; @@ -145,7 +145,7 @@ public function update(Page $page, array $input): Page if ($htmlChanged || $nameChanged || $markdownChanged || $summary) { $this->revisionRepo->storeNewForPage($page, $summary); } - + if (array_key_exists('image', $input)) { $this->baseRepo->updateCoverImage($page, $input['image'], $input['image'] === null); } diff --git a/app/Entities/Tools/BookContents.php b/app/Entities/Tools/BookContents.php index 4bbab626520..a976a8a7a67 100644 --- a/app/Entities/Tools/BookContents.php +++ b/app/Entities/Tools/BookContents.php @@ -97,7 +97,7 @@ protected function getPages(bool $showDrafts = false, bool $getPageContent = fal } if (!$showDrafts) { - $query->where('draft', '=', false); + $query->where('entity_page_data.draft', '=', false); } return $query->where('book_id', '=', $this->book->id)->get(); diff --git a/app/Entities/Tools/EntityCover.php b/app/Entities/Tools/EntityCover.php index 1e8fce201dd..f52c3621e8d 100644 --- a/app/Entities/Tools/EntityCover.php +++ b/app/Entities/Tools/EntityCover.php @@ -4,6 +4,7 @@ use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; +use BookStack\Entities\Models\Page; use BookStack\Uploads\Image; use Exception; use Illuminate\Database\Eloquent\Builder; @@ -11,7 +12,7 @@ class EntityCover { public function __construct( - protected Book|Bookshelf $entity, + protected Book|Bookshelf|Page $entity, ) { } @@ -33,6 +34,7 @@ public function exists(): bool */ public function getImage(): Image|null { + if ($this->entity->image_id === null) { return null; } diff --git a/app/Entities/Tools/EntityHtmlDescription.php b/app/Entities/Tools/EntityHtmlDescription.php index b14deb257a7..b48397bf4a4 100644 --- a/app/Entities/Tools/EntityHtmlDescription.php +++ b/app/Entities/Tools/EntityHtmlDescription.php @@ -5,6 +5,7 @@ use BookStack\Entities\Models\Book; use BookStack\Entities\Models\Bookshelf; use BookStack\Entities\Models\Chapter; +use BookStack\Entities\Models\Page; use BookStack\Util\HtmlContentFilter; class EntityHtmlDescription @@ -13,7 +14,7 @@ class EntityHtmlDescription protected string $plain = ''; public function __construct( - protected Book|Chapter|Bookshelf $entity, + protected Book|Chapter|Bookshelf|Page $entity, ) { $this->html = $this->entity->description_html ?? ''; $this->plain = $this->entity->description ?? ''; diff --git a/database/migrations/2026_04_11_220254_add_image_id_to_entity_page_data.php b/database/migrations/2026_04_11_220254_add_image_id_to_entity_page_data.php new file mode 100755 index 00000000000..806f89ceed4 --- /dev/null +++ b/database/migrations/2026_04_11_220254_add_image_id_to_entity_page_data.php @@ -0,0 +1,30 @@ +unsignedInteger('image_id')->nullable(); + $table->foreign('image_id')->references('id')->on('images')->onDelete('cascade'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('entity_page_data', function (Blueprint $table) { + $table->dropForeign(['image_id']); + $table->dropColumn('image_id'); + }); + } +}; diff --git a/resources/views/chapters/parts/show-sidebar-section-actions.blade.php b/resources/views/chapters/parts/show-sidebar-section-actions.blade.php index 55df999a22c..06d52e113fe 100644 --- a/resources/views/chapters/parts/show-sidebar-section-actions.blade.php +++ b/resources/views/chapters/parts/show-sidebar-section-actions.blade.php @@ -2,6 +2,7 @@
{{ trans('common.actions') }}