From 12290ae1aafcfed944ef3dc5c332ab35bc3d633c Mon Sep 17 00:00:00 2001 From: Brij Mandaliya Date: Thu, 24 Oct 2024 19:02:09 +0530 Subject: [PATCH 1/4] 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 2/4] 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 3/4] 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 4/4] 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' => [