From 57779a2419e00e0180cdc29862db6a5ef40704b1 Mon Sep 17 00:00:00 2001 From: Brij Mandaliya Date: Thu, 23 Apr 2026 10:51:20 +0530 Subject: [PATCH] Build: Add Support of cover image for pages and also grid view for list of pages (#21) * 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 * fix: lint php * set to get default page cover image from env * fix: lint php * 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 * fix the link. * fix link * refactor(entities): migrate draft and cover image handling to entity_container_data table - Move draft status from entities table to entity_container_data table - Add fillable fields to EntityContainerData for image_id, sort_rule_id, and description fields - Update all draft queries to use entity_page_data.draft join condition - Enhance ReferenceChangeContext to track URL and permalink mappings during cloning - Update ReferenceUpdater to process reference changes in descriptions and page content - Modify cover image access to use coverInfo() method instead of cover property - Fix entity-list-item class naming from chapter to page - Update copy tests to verify reference updating during entity cloning BREAKING CHANGE: Draft status is now stored in entity_container_data table instead of entities table * fix lint * fix analysis --- app/App/HomeController.php | 6 +- app/Config/setting-defaults.php | 2 + .../Controllers/ChapterController.php | 3 + app/Entities/Controllers/PageController.php | 6 ++ app/Entities/Models/Entity.php | 2 + app/Entities/Models/EntityContainerData.php | 9 +++ app/Entities/Models/EntityPageData.php | 1 + app/Entities/Models/Page.php | 34 ++++++++++- app/Entities/Queries/PageQueries.php | 48 +++++++++++----- app/Entities/Repos/PageRepo.php | 8 +++ app/Entities/Tools/BookContents.php | 4 +- app/Entities/Tools/Cloner.php | 3 +- app/Entities/Tools/EntityCover.php | 4 +- app/Entities/Tools/EntityHtmlDescription.php | 3 +- app/Permissions/PermissionApplicator.php | 6 +- app/References/ReferenceChangeContext.php | 18 ++++++ app/References/ReferenceUpdater.php | 57 +++++++++++++++++-- app/Search/SearchController.php | 2 +- app/Settings/SettingService.php | 5 ++ .../Controllers/UserPreferencesController.php | 2 +- .../Queries/UserRecentlyCreatedContent.php | 2 +- ...0_24_074404_add_image_id_in_page_table.php | 28 +++++++++ ...20254_add_image_id_to_entity_page_data.php | 30 ++++++++++ resources/sass/_colors.scss | 3 + .../show-sidebar-section-actions.blade.php | 1 + resources/views/chapters/show.blade.php | 18 ++++-- resources/views/pages/edit.blade.php | 2 +- .../pages/parts/editor-toolbox.blade.php | 15 +++++ .../views/pages/parts/list-item.blade.php | 15 +++-- tests/Entity/CopyTest.php | 23 +++++--- tests/Entity/PageTest.php | 10 ++++ 31 files changed, 315 insertions(+), 55 deletions(-) create mode 100755 database/migrations/2024_10_24_074404_add_image_id_in_page_table.php create mode 100755 database/migrations/2026_04_11_220254_add_image_id_to_entity_page_data.php diff --git a/app/App/HomeController.php b/app/App/HomeController.php index 00e2db3df43..ac3bd765661 100644 --- a/app/App/HomeController.php +++ b/app/App/HomeController.php @@ -45,7 +45,7 @@ public function index( : $this->queries->books->visibleForList()->orderBy('created_at', 'desc')->take(12 * $recentFactor)->get(); $favourites = $topFavourites->run(6); $recentlyUpdatedPages = $this->queries->pages->visibleForList() - ->where('draft', false) + ->where('entity_page_data.draft', false) ->orderBy('updated_at', 'desc') ->take($favourites->count() > 0 ? 5 : 10) ->get(); @@ -102,7 +102,9 @@ public function index( $homepageSetting = setting('app-homepage', '0:'); $id = intval(explode(':', $homepageSetting)[0]); /** @var Page $customHomepage */ - $customHomepage = $this->queries->pages->start()->where('draft', '=', false)->findOrFail($id); + $customHomepage = $this->queries->pages->start() + ->where('entity_page_data.draft', '=', false) + ->findOrFail($id); $pageContent = new PageContent($customHomepage); $customHomepage->html = $pageContent->render(false); diff --git a/app/Config/setting-defaults.php b/app/Config/setting-defaults.php index 2f270b283a2..827cdb1addd 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' => [ @@ -41,6 +42,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'), 'notifications#comment-mentions' => true, ], diff --git a/app/Entities/Controllers/ChapterController.php b/app/Entities/Controllers/ChapterController.php index 878ee42b5ae..b96af4a2b7b 100644 --- a/app/Entities/Controllers/ChapterController.php +++ b/app/Entities/Controllers/ChapterController.php @@ -77,8 +77,10 @@ public function store(Request $request, string $bookSlug) */ public function show(string $bookSlug, string $chapterSlug) { + $view = setting()->getForCurrentUser(key: 'pages_view_type'); try { $chapter = $this->queries->findVisibleBySlugsOrFail($bookSlug, $chapterSlug); + $this->checkOwnablePermission('chapter-view', $chapter); } catch (NotFoundException $exception) { $chapter = $this->entityQueries->findVisibleByOldSlugs('chapter', $chapterSlug, $bookSlug); if (is_null($chapter)) { @@ -105,6 +107,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 a648bc29882..5d30b03216a 100644 --- a/app/Entities/Controllers/PageController.php +++ b/app/Entities/Controllers/PageController.php @@ -220,6 +220,12 @@ public function update(Request $request, string $bookSlug, string $pageSlug) $page = $this->queries->findVisibleBySlugsOrFail($bookSlug, $pageSlug); $this->checkOwnablePermission(Permission::PageUpdate, $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/Entity.php b/app/Entities/Models/Entity.php index 47e13462691..e3c2839c8c0 100644 --- a/app/Entities/Models/Entity.php +++ b/app/Entities/Models/Entity.php @@ -47,6 +47,8 @@ * @property int|null $updated_by * @property int|null $owned_by * @property Collection $tags + * @property string $description + * @property string $description_html * * @method static Entity|Builder visible() * @method static Builder withLastView() diff --git a/app/Entities/Models/EntityContainerData.php b/app/Entities/Models/EntityContainerData.php index 21bace7513f..6b4c54f7f17 100644 --- a/app/Entities/Models/EntityContainerData.php +++ b/app/Entities/Models/EntityContainerData.php @@ -19,6 +19,15 @@ class EntityContainerData extends Model public $timestamps = false; protected $primaryKey = 'entity_id'; public $incrementing = false; + protected $fillable = [ + 'entity_id', + 'entity_type', + 'description', + 'description_html', + 'default_template_id', + 'image_id', + 'sort_rule_id', + ]; public static array $fields = [ 'description', 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 a1d3fc7b40d..d4d6d32df7e 100644 --- a/app/Entities/Models/Page.php +++ b/app/Entities/Models/Page.php @@ -2,9 +2,14 @@ namespace BookStack\Entities\Models; +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; use BookStack\Uploads\Attachment; +use Exception; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -23,22 +28,25 @@ * @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 HasDescriptionInterface, HasCoverInterface { use HasFactory; + // use ContainerTrait; + public string $textField = 'text'; public string $htmlField = 'html'; - protected $hidden = ['html', 'markdown', 'text', 'pivot', 'deleted_at', 'entity_id', 'entity_type']; + protected $hidden = ['html', 'markdown', 'text', 'pivot', 'deleted_at', 'entity_id', 'entity_type']; protected $fillable = ['name', 'priority']; protected $casts = [ - 'draft' => 'boolean', + 'draft' => 'boolean', 'template' => 'boolean', ]; @@ -145,6 +153,26 @@ public function forJsonDisplay(): self return $refreshed; } + + public function descriptionInfo(): EntityHtmlDescription + { + return new EntityHtmlDescription($this); + } + + public function cover(): BelongsTo + { + return $this->belongsTo(Image::class, 'image_id'); + } + + public function coverInfo(): EntityCover + { + return new EntityCover($this); + } + + public function coverImageTypeKey(): string + { + return 'cover_page'; + } /** * @return HasOne */ diff --git a/app/Entities/Queries/PageQueries.php b/app/Entities/Queries/PageQueries.php index f4ecee2dc08..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', + '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', + ] + )); } /** @@ -92,30 +102,35 @@ public function visibleForContent(): Builder public function visibleForChapterList(int $chapterId): Builder { - return $this->visibleForList() - ->where('chapter_id', '=', $chapterId) - ->orderBy('draft', 'desc') - ->orderBy('priority', 'asc'); + return $this->visibleForListWithCover() + ->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 @@ -127,4 +142,9 @@ protected function mergeBookSlugForSelect(array $columns): array ->whereColumn('books.id', '=', 'entities.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 bc590785d93..23c408e5397 100644 --- a/app/Entities/Repos/PageRepo.php +++ b/app/Entities/Repos/PageRepo.php @@ -88,6 +88,10 @@ 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); + } + $draft = $this->baseRepo->update($draft, $input); $draft->rebuildPermissions(); @@ -142,6 +146,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); $this->baseRepo->sortParent($page); diff --git a/app/Entities/Tools/BookContents.php b/app/Entities/Tools/BookContents.php index 4bbab626520..c71e9e069a2 100644 --- a/app/Entities/Tools/BookContents.php +++ b/app/Entities/Tools/BookContents.php @@ -25,7 +25,7 @@ public function __construct( public function getLastPriority(): int { $maxPage = $this->book->pages() - ->where('draft', '=', false) + ->where('entity_page_data.draft', '=', false) ->whereDoesntHave('chapter') ->max('priority'); @@ -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/Cloner.php b/app/Entities/Tools/Cloner.php index 64c48c351ae..468eaa1da3c 100644 --- a/app/Entities/Tools/Cloner.php +++ b/app/Entities/Tools/Cloner.php @@ -106,6 +106,7 @@ protected function createBookClone(Book $original, string $newName): Book // Clone book $copyBook = $this->bookRepo->create($bookDetails); + $this->referenceChangeContext->add($original, $copyBook); // Clone contents $directChildren = $original->getDirectVisibleChildren(); @@ -127,8 +128,6 @@ protected function createBookClone(Book $original, string $newName): Book } } - $this->referenceChangeContext->add($original, $copyBook); - return $copyBook; } diff --git a/app/Entities/Tools/EntityCover.php b/app/Entities/Tools/EntityCover.php index 1e8fce201dd..878c673ac74 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/app/Permissions/PermissionApplicator.php b/app/Permissions/PermissionApplicator.php index c44a18a4d53..a2a258902f0 100644 --- a/app/Permissions/PermissionApplicator.php +++ b/app/Permissions/PermissionApplicator.php @@ -117,10 +117,10 @@ public function restrictEntityQuery(Builder $query): Builder public function restrictDraftsOnPageQuery(Builder $query): Builder { return $query->where(function (Builder $query) { - $query->where('draft', '=', false) + $query->where('entity_page_data.draft', '=', false) ->orWhere(function (Builder $query) { - $query->where('draft', '=', true) - ->where('owned_by', '=', $this->currentUser()->id); + $query->where('entity_page_data.draft', '=', true) + ->where('entities.owned_by', '=', $this->currentUser()->id); }); }); } diff --git a/app/References/ReferenceChangeContext.php b/app/References/ReferenceChangeContext.php index 27de0e2d24c..21853eb94b1 100644 --- a/app/References/ReferenceChangeContext.php +++ b/app/References/ReferenceChangeContext.php @@ -12,9 +12,17 @@ class ReferenceChangeContext */ protected array $changes = []; + protected array $urlMap = []; + protected array $permalinkMap = []; + public function add(Entity $oldEntity, Entity $newEntity): void { $this->changes[] = [$oldEntity, $newEntity]; + $this->urlMap[$oldEntity->getUrl()] = $newEntity->getUrl(); + + if (method_exists($oldEntity, 'getPermalink') && method_exists($newEntity, 'getPermalink')) { + $this->permalinkMap[$oldEntity->getPermalink()] = $newEntity->getPermalink(); + } } /** @@ -42,4 +50,14 @@ public function getNewForOld(Entity $oldEntity): ?Entity } return null; } + + public function getUrlMap(): array + { + return $this->urlMap; + } + + public function getPermalinkMap(): array + { + return $this->permalinkMap; + } } diff --git a/app/References/ReferenceUpdater.php b/app/References/ReferenceUpdater.php index 42de72fde04..d19723ef095 100644 --- a/app/References/ReferenceUpdater.php +++ b/app/References/ReferenceUpdater.php @@ -3,9 +3,12 @@ namespace BookStack\References; use BookStack\Entities\Models\Book; +use BookStack\Entities\Models\Bookshelf; +use BookStack\Entities\Models\Chapter; use BookStack\Entities\Models\HasDescriptionInterface; use BookStack\Entities\Models\Entity; use BookStack\Entities\Models\Page; +use Illuminate\Support\Facades\DB; use BookStack\Entities\Repos\RevisionRepo; use BookStack\Util\HtmlDocument; @@ -67,6 +70,17 @@ public function changeReferencesUsingContext(ReferenceChangeContext $context): v $reference->to_type = $newToEntity->getMorphClass(); $reference->save(); } + + // Handle embedded HTML links within content fields + foreach ($context->getUrlMap() as $oldUrl => $newUrl) { + if ($new instanceof Page) { + $this->updateReferencesWithinPage($new, $oldUrl, $newUrl); + } + // Only for Chapter, Book, or Bookshelf - entities with descriptions + if (($new instanceof Chapter) || ($new instanceof Book) || ($new instanceof Bookshelf)) { + $this->updateReferencesWithinDescription($new, $oldUrl, $newUrl); + } + } } } @@ -104,7 +118,7 @@ protected function updateReferencesWithinEntity(Entity $entity, string $oldLink, $this->updateReferencesWithinPage($entity, $oldLink, $newLink); } - if ($entity instanceof HasDescriptionInterface) { + if (($entity instanceof Chapter) || ($entity instanceof Book) || ($entity instanceof Bookshelf)) { $this->updateReferencesWithinDescription($entity, $oldLink, $newLink); } } @@ -112,9 +126,35 @@ protected function updateReferencesWithinEntity(Entity $entity, string $oldLink, protected function updateReferencesWithinDescription(Entity&HasDescriptionInterface $entity, string $oldLink, string $newLink): void { $description = $entity->descriptionInfo(); - $html = $this->updateLinksInHtml($description->getHtml(true) ?: '', $oldLink, $newLink); + $originalHtml = $description->getHtml(true) ?: ''; + $html = $this->updateLinksInHtml($originalHtml, $oldLink, $newLink); $description->set($html); - $entity->save(); + + $plainText = $description->getPlain(); + $entity->description_html = $html; + $entity->description = $plainText; + + $existing = DB::table('entity_container_data') + ->where('entity_id', $entity->id) + ->where('entity_type', $entity->getMorphClass()) + ->first(); + + if ($existing) { + DB::table('entity_container_data') + ->where('entity_id', $entity->id) + ->where('entity_type', $entity->getMorphClass()) + ->update([ + 'description_html' => $html, + 'description' => $plainText, + ]); + } else { + DB::table('entity_container_data')->insert([ + 'entity_id' => $entity->id, + 'entity_type' => $entity->getMorphClass(), + 'description_html' => $html, + 'description' => $plainText, + ]); + } } protected function updateReferencesWithinPage(Page $page, string $oldLink, string $newLink): void @@ -159,10 +199,15 @@ protected function updateLinksInHtml(string $html, string $oldLink, string $newL /** @var \DOMElement $anchor */ foreach ($anchors as $anchor) { $link = $anchor->getAttribute('href'); - $updated = str_ireplace($oldLink, $newLink, $link); - $anchor->setAttribute('href', $updated); + $isExactMatch = strcasecmp($link, $oldLink) === 0; + $isPrefixMatch = strcasecmp(substr($link, 0, strlen($oldLink)), $oldLink) === 0 && strlen($link) > strlen($oldLink) && $link[strlen($oldLink)] === '/'; + if (!$isExactMatch && !$isPrefixMatch) { + continue; + } + $anchor->setAttribute('href', $newLink . ($isPrefixMatch ? substr($link, strlen($oldLink)) : '')); } - return $doc->getBodyInnerHtml(); + $result = $doc->getBodyInnerHtml(); + return $result; } } diff --git a/app/Search/SearchController.php b/app/Search/SearchController.php index 348d44a427f..f7b7be61d01 100644 --- a/app/Search/SearchController.php +++ b/app/Search/SearchController.php @@ -101,7 +101,7 @@ public function templatesForSelector(Request $request) $entities = $this->searchRunner->searchEntities($searchOptions, 'page', 1, 20)['results']; } else { $entities = $this->pageQueries->visibleTemplates() - ->where('draft', '=', false) + ->where('entity_page_data.draft', '=', false) ->orderBy('updated_at', 'desc') ->take(20) ->get(); diff --git a/app/Settings/SettingService.php b/app/Settings/SettingService.php index e0b13618012..5b29ea8d1b1 100644 --- a/app/Settings/SettingService.php +++ b/app/Settings/SettingService.php @@ -291,4 +291,9 @@ public function flushCache(): void { $this->localCache = []; } + + public function getDefaultPageCoverImage() + { + return config('setting-defaults.default_page_cover_image'); + } } 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/app/Users/Queries/UserRecentlyCreatedContent.php b/app/Users/Queries/UserRecentlyCreatedContent.php index 23850e07201..55d0ce57ccb 100644 --- a/app/Users/Queries/UserRecentlyCreatedContent.php +++ b/app/Users/Queries/UserRecentlyCreatedContent.php @@ -30,7 +30,7 @@ public function run(User $user, int $count): array }; return [ - 'pages' => $query($this->queries->pages->visibleForList()->where('draft', '=', false)), + 'pages' => $query($this->queries->pages->visibleForList()->where('entity_page_data.draft', '=', false)), 'chapters' => $query($this->queries->chapters->visibleForList()), 'books' => $query($this->queries->books->visibleForList()), 'shelves' => $query($this->queries->shelves->visibleForList()), 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/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/sass/_colors.scss b/resources/sass/_colors.scss index bf7a7a0fc03..917a95e9ff1 100644 --- a/resources/sass/_colors.scss +++ b/resources/sass/_colors.scss @@ -105,3 +105,6 @@ .bg-bookshelf { background-color: var(--color-bookshelf); } +.bg-page { + background-color: var(--color-page); +} 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') }}