Professional searchable page library.

* Clear Live, Draft and Homepage indicators.
* Direct Edit page, Edit content and View page actions.
* Page editor divided into:
    * Page details
    * SEO and URL
    * Publishing
* Automatic, unique URL slugs.
* Publish and expiry scheduling.
* Page templates: Default, Landing and Full Width.
* Homepage replacement handled automatically.
* Current homepage protected from accidental deletion.
* Page duplication includes its complete content structure.
* Content composer now works from the actual page record and page ID.
* Clearer section-based terminology for editors.
* Reusable content remains available as an advanced content library rather than dominating the workflow.

The package contains the eight complete changed files, preserving their project paths. It is not a patch collection.
This commit is contained in:
Mark Gregory
2026-07-28 13:35:06 +01:00
parent 1764e5d8f4
commit b738ff908b
8 changed files with 323 additions and 47 deletions
@@ -11,6 +11,7 @@ use Illuminate\Validation\Rule;
use Illuminate\View\View;
use Modules\Content\Models\ContentDocument;
use Modules\Content\Models\PageContent;
use Modules\Pages\Models\Page;
final class PageCompositionController extends Controller
{
@@ -35,6 +36,35 @@ final class PageCompositionController extends Controller
return view('content::admin.composition', compact('pageKey', 'placements', 'documents'));
}
public function editPage(Page $page): View
{
$placements = PageContent::query()
->with('document.type')
->where(fn ($query) => $query->where('page_id', $page->id)->orWhere('page_key', $page->slug))
->orderByRaw("CASE region WHEN 'header' THEN 1 WHEN 'body' THEN 2 ELSE 3 END")
->orderBy('sort_order')
->get();
PageContent::query()
->whereNull('page_id')
->where('page_key', $page->slug)
->update(['page_id' => $page->id]);
$documents = ContentDocument::query()
->with('type')
->withCount('placements')
->orderBy('title')
->get();
return view('content::admin.composition', [
'pageKey' => $page->slug,
'page' => $page,
'placements' => $placements->groupBy('region'),
'documents' => $documents,
]);
}
public function preview(string $pageKey): View
{
abort_unless($pageKey === 'home', 404, 'This phase currently provides the Saasland homepage preview.');
@@ -52,8 +82,10 @@ final class PageCompositionController extends Controller
$document = ContentDocument::query()->with('type')->findOrFail($validated['content_document_id']);
abort_unless(in_array($validated['region'], $document->type?->allowed_regions ?? [], true), 422, 'This content type is not allowed in the selected region.');
$nextOrder = ((int) PageContent::query()->where('page_key', $pageKey)->where('region', $validated['region'])->max('sort_order')) + 10;
$page = Page::query()->where('slug', $pageKey)->first();
$nextOrder = ((int) PageContent::query()->where(fn ($query) => $query->where('page_key', $pageKey)->when($page, fn ($q) => $q->orWhere('page_id', $page->id)))->where('region', $validated['region'])->max('sort_order')) + 10;
PageContent::query()->create([
'page_id' => $page?->id,
'page_key' => $pageKey,
'content_document_id' => $document->id,
'region' => $validated['region'],