Layouts: Started edit form ui, added block IDs

This commit is contained in:
Dan Brown
2026-08-12 15:43:00 +01:00
parent 78610a2a14
commit 10cf128598
46 changed files with 184 additions and 2 deletions
+2
View File
@@ -18,11 +18,13 @@ class LayoutController
{
$namedLocations = $this->viewBlocks->getNamedLocations();
$locationName = $namedLocations[$location] ?? $location;
$blocks = $this->viewBlocks->getForLocation($location);
return view('settings.layouts.edit', [
'location' => $location,
'locationName' => $locationName,
'namedLocations' => $namedLocations,
'blocks' => $blocks,
]);
}
+9
View File
@@ -4,9 +4,18 @@ namespace BookStack\View;
abstract class ViewBlock implements ViewBlockInterface
{
protected string $id;
protected string $view;
protected string $labelTranslationKey;
/**
* @inheritDoc
*/
public function getId(): string
{
return $this->id;
}
/**
* @inheritDoc
*/
+1
View File
@@ -110,6 +110,7 @@ class ViewBlockDefaults
/**
* Get the locations for all default blocks.
* @return string[]
*/
public static function getLocations(): array
{
+5
View File
@@ -4,6 +4,11 @@ namespace BookStack\View;
interface ViewBlockInterface
{
/**
* Get the unique ID for this block.
*/
public function getId(): string;
/**
* Get the label for this block.
*/
+23 -1
View File
@@ -34,7 +34,7 @@ class ViewBlockManager
* @return ViewBlockInterface[]
* @throws BindingResolutionException
*/
public function getForLocation(string $location, string $position): array
public function getForLocationAndPosition(string $location, string $position): array
{
$defaults = ViewBlockDefaults::getForLocation($location)[$position] ?? [];
$registered = $this->blocksByLocationAndPosition[$location][$position] ?? [];
@@ -45,6 +45,28 @@ class ViewBlockManager
}, $sections);
}
/**
* Get all blocks registered for a given location, as sets of arrays
* keyed by position.
* @return array<string, ViewBlockInterface[]>
* @throws BindingResolutionException
*/
public function getForLocation(string $location): array
{
$defaults = ViewBlockDefaults::getForLocation($location) ?? [];
$registered = $this->blocksByLocationAndPosition[$location] ?? [];
$sections = array_merge_recursive($defaults, $registered);
$instances = [];
foreach ($sections as $position => $blocks) {
$instances[$position] = array_map(function (string $className) {
return app()->make($className);
}, $blocks);
}
return $instances;
}
/**
* Get the names of all locations where blocks are registered.
* Returns an array where the keys are location strings, and the
@@ -7,6 +7,7 @@ use Illuminate\Http\Request;
class BooksIndexActions extends ViewBlock
{
protected string $id = 'builtin_books-index-actions';
protected string $view = 'books.parts.index-sidebar-section-actions';
protected string $labelTranslationKey = 'common.actions';
+1
View File
@@ -8,6 +8,7 @@ use Illuminate\Http\Request;
class BooksIndexNew extends ViewBlock
{
protected string $id = 'builtin_books-index-new';
protected string $view = 'books.parts.index-sidebar-section-new';
protected string $labelTranslationKey = 'entities.books_new';
@@ -8,6 +8,7 @@ use Illuminate\Http\Request;
class BooksIndexPopular extends ViewBlock
{
protected string $id = 'builtin_books-index-popular';
protected string $view = 'books.parts.index-sidebar-section-popular';
protected string $labelTranslationKey = 'entities.books_popular';
@@ -8,6 +8,7 @@ use Illuminate\Http\Request;
class BooksIndexRecents extends ViewBlock
{
protected string $id = 'builtin_books-index-recents';
protected string $view = 'books.parts.index-sidebar-section-recents';
protected string $labelTranslationKey = 'entities.recently_viewed';
+1
View File
@@ -9,6 +9,7 @@ use Illuminate\Http\Request;
class BooksShowActions extends ViewBlock
{
protected string $id = 'builtin_books-show-actions';
protected string $view = 'books.parts.show-sidebar-section-actions';
protected string $labelTranslationKey = 'common.actions';
@@ -9,6 +9,7 @@ use Illuminate\Http\Request;
class BooksShowActivity extends ViewBlock
{
protected string $id = 'builtin_books-show-activity';
protected string $view = 'books.parts.show-sidebar-section-activity';
protected string $labelTranslationKey = 'entities.recent_activity';
+1
View File
@@ -10,6 +10,7 @@ use Illuminate\Http\Request;
class BooksShowDetails extends ViewBlock
{
protected string $id = 'builtin_books-show-details';
protected string $view = 'books.parts.show-sidebar-section-details';
protected string $labelTranslationKey = 'common.details';
@@ -7,6 +7,7 @@ use Illuminate\Http\Request;
class BooksShowSearchForm extends ViewBlock
{
protected string $id = 'builtin_books-show-search-form';
protected string $view = 'entities.search-form';
protected string $labelTranslationKey = 'common.search';
+1
View File
@@ -8,6 +8,7 @@ use Illuminate\Http\Request;
class BooksShowShelves extends ViewBlock
{
protected string $id = 'builtin_books-show-shelves';
protected string $view = 'books.parts.show-sidebar-section-shelves';
protected string $labelTranslationKey = 'entities.shelves';
+1
View File
@@ -7,6 +7,7 @@ use Illuminate\Http\Request;
class BooksShowTags extends ViewBlock
{
protected string $id = 'builtin_books-show-tags';
protected string $view = 'books.parts.show-sidebar-section-tags';
protected string $labelTranslationKey = 'entities.tags';
@@ -9,6 +9,7 @@ use Illuminate\Http\Request;
class ChaptersShowActions extends ViewBlock
{
protected string $id = 'builtin_chapters-show-actions';
protected string $view = 'chapters.parts.show-sidebar-section-actions';
protected string $labelTranslationKey = 'common.actions';
@@ -8,6 +8,7 @@ use Illuminate\Http\Request;
class ChaptersShowBookTree extends ViewBlock
{
protected string $id = 'builtin_chapters-show-book-tree';
protected string $view = 'entities.book-tree';
protected string $labelTranslationKey = 'entities.books_navigation';
@@ -11,6 +11,7 @@ use Illuminate\Http\Request;
class ChaptersShowDetails extends ViewBlock
{
protected string $id = 'builtin_chapters-show-details';
protected string $view = 'chapters.parts.show-sidebar-section-details';
protected string $labelTranslationKey = 'common.details';
@@ -7,6 +7,7 @@ use Illuminate\Http\Request;
class ChaptersShowSearchForm extends ViewBlock
{
protected string $id = 'builtin_chapters-show-search-form';
protected string $view = 'entities.search-form';
protected string $labelTranslationKey = 'common.search';
+1
View File
@@ -8,6 +8,7 @@ use Illuminate\Http\Request;
class ChaptersShowTags extends ViewBlock
{
protected string $id = 'builtin_chapters-show-tags';
protected string $view = 'chapters.parts.show-sidebar-section-tags';
protected string $labelTranslationKey = 'entities.tags';
+1
View File
@@ -6,6 +6,7 @@ use BookStack\View\ViewBlock;
class HomeActions extends ViewBlock
{
protected string $id = 'builtin_home-actions';
protected string $view = 'home.parts.configured-section-actions';
protected string $labelTranslationKey = 'common.actions';
@@ -13,6 +13,11 @@ class HomeRecentActivity implements ViewBlockInterface
) {
}
public function getId(): string
{
return 'builtin_home-recent-activity';
}
public function getLabel(): string
{
return trans('entities.recent_activity');
+5
View File
@@ -12,6 +12,11 @@ class HomeRecentDrafts implements ViewBlockInterface
) {
}
public function getId(): string
{
return 'builtin_home-recent-drafts';
}
public function getLabel(): string
{
return trans('entities.my_recent_drafts');
@@ -13,6 +13,11 @@ class HomeRecentlyUpdatedPages implements ViewBlockInterface
) {
}
public function getId(): string
{
return 'builtin_home-recently-updated-pages';
}
public function getLabel(): string
{
return trans('entities.recently_updated_pages');
@@ -16,6 +16,11 @@ class HomeRecentlyViewedOrRecentBooks implements ViewBlockInterface
) {
}
public function getId(): string
{
return 'builtin_home-recently-viewed-or-recent-books';
}
public function getLabel(): string
{
$key = user()->isGuest() ? 'books_recent' : 'my_recently_viewed';
@@ -13,6 +13,11 @@ class HomeTopFavourites implements ViewBlockInterface
) {
}
public function getId(): string
{
return 'builtin_home-top-favourites';
}
public function getLabel(): string
{
return trans('entities.my_most_viewed_favourites');
+1
View File
@@ -9,6 +9,7 @@ use Illuminate\Http\Request;
class PagesShowActions extends ViewBlock
{
protected string $id = 'builtin_pages-show-actions';
protected string $view = 'pages.parts.show-sidebar-section-actions';
protected string $labelTranslationKey = 'common.actions';
@@ -8,6 +8,7 @@ use Illuminate\Http\Request;
class PagesShowAttachments extends ViewBlock
{
protected string $id = 'builtin_pages-show-attachments';
protected string $view = 'pages.parts.show-sidebar-section-attachments';
protected string $labelTranslationKey = 'entities.pages_attachments';
@@ -8,6 +8,7 @@ use Illuminate\Http\Request;
class PagesShowBookTree extends ViewBlock
{
protected string $id = 'builtin_pages-show-book-tree';
protected string $view = 'entities.book-tree';
protected string $labelTranslationKey = 'entities.books_navigation';
+1
View File
@@ -11,6 +11,7 @@ use Illuminate\Http\Request;
class PagesShowDetails extends ViewBlock
{
protected string $id = 'builtin_pages-show-details';
protected string $view = 'pages.parts.show-sidebar-section-details';
protected string $labelTranslationKey = 'common.details';
+1
View File
@@ -9,6 +9,7 @@ use Illuminate\Http\Request;
class PagesShowPageNav extends ViewBlock
{
protected string $id = 'builtin_pages-show-page-nav';
protected string $view = 'pages.parts.show-sidebar-section-page-nav';
protected string $labelTranslationKey = 'entities.pages_navigation';
+1
View File
@@ -8,6 +8,7 @@ use Illuminate\Http\Request;
class PagesShowTags extends ViewBlock
{
protected string $id = 'builtin_pages-show-tags';
protected string $view = 'pages.parts.show-sidebar-section-tags';
protected string $labelTranslationKey = 'entities.tags';
@@ -7,6 +7,7 @@ use Illuminate\Http\Request;
class ShelvesIndexActions extends ViewBlock
{
protected string $id = 'builtin_shelves-index-actions';
protected string $view = 'shelves.parts.index-sidebar-section-actions';
protected string $labelTranslationKey = 'common.actions';
+1
View File
@@ -8,6 +8,7 @@ use Illuminate\Http\Request;
class ShelvesIndexNew extends ViewBlock
{
protected string $id = 'builtin_shelves-index-new';
protected string $view = 'shelves.parts.index-sidebar-section-new';
protected string $labelTranslationKey = 'entities.shelves_new';
@@ -8,6 +8,7 @@ use Illuminate\Http\Request;
class ShelvesIndexPopular extends ViewBlock
{
protected string $id = 'builtin_shelves-index-popular';
protected string $view = 'shelves.parts.index-sidebar-section-popular';
protected string $labelTranslationKey = 'entities.shelves_popular';
@@ -8,6 +8,7 @@ use Illuminate\Http\Request;
class ShelvesIndexRecents extends ViewBlock
{
protected string $id = 'builtin_shelves-index-recents';
protected string $view = 'shelves.parts.index-sidebar-section-recents';
protected string $labelTranslationKey = 'entities.recently_viewed';
@@ -8,6 +8,7 @@ use Illuminate\Http\Request;
class ShelvesShowActions extends ViewBlock
{
protected string $id = 'builtin_shelves-show-actions';
protected string $view = 'shelves.parts.show-sidebar-section-actions';
protected string $labelTranslationKey = 'common.actions';
@@ -9,6 +9,7 @@ use Illuminate\Http\Request;
class ShelvesShowActivity extends ViewBlock
{
protected string $id = 'builtin_shelves-show-activity';
protected string $view = 'shelves.parts.show-sidebar-section-activity';
protected string $labelTranslationKey = 'entities.recent_activity';
@@ -9,6 +9,7 @@ use Illuminate\Http\Request;
class ShelvesShowDetails extends ViewBlock
{
protected string $id = 'builtin_shelves-show-details';
protected string $view = 'shelves.parts.show-sidebar-section-details';
protected string $labelTranslationKey = 'common.details';
+1
View File
@@ -7,6 +7,7 @@ use Illuminate\Http\Request;
class ShelvesShowTags extends ViewBlock
{
protected string $id = 'builtin_shelves-show-tags';
protected string $view = 'shelves.parts.show-sidebar-section-tags';
protected string $labelTranslationKey = 'entities.tags';
+5
View File
@@ -31,6 +31,11 @@ return [
'layout_edit_save' => 'Save Layout',
'layout_edit_layouts' => 'Layouts',
'layout_edit_back_to_preferences' => 'Back to Preferences',
'layout_edit_left' => 'Left',
'layout_edit_right' => 'Right',
'layout_edit_center' => 'Center',
'layout_edit_unused' => 'Unused',
'layout_edit_empty' => 'No sections to display',
'notifications' => 'Notification Preferences',
'notifications_desc' => 'Control the email notifications you receive when certain activity is performed within the system.',
+1 -1
View File
@@ -5,6 +5,6 @@ $position - string
@php
$sectionVars = get_defined_vars();
@endphp
@foreach($viewBlocks->getForLocation($location, $position) as $section)
@foreach($viewBlocks->getForLocationAndPosition($location, $position) as $section)
@include($section->getView($sectionVars), $section->withData($sectionVars))
@endforeach
@@ -30,6 +30,16 @@
{{ method_field('PUT') }}
{{ csrf_field() }}
<div class="flex-container-row gap-m">
@include('settings.layouts.parts.block-column', ['columnBlocks' => $blocks['left'] ?? [], 'label' => trans('preferences.layout_edit_left'), 'id' => 'left'])
@include('settings.layouts.parts.block-column', ['columnBlocks' => $blocks['center'] ?? [], 'label' => trans('preferences.layout_edit_center'), 'id' => 'center'])
@include('settings.layouts.parts.block-column', ['columnBlocks' => $blocks['right'] ?? [], 'label' => trans('preferences.layout_edit_right'), 'id' => 'right'])
</div>
<div>
@include('settings.layouts.parts.block-column', ['columnBlocks' => $blocks['unused'] ?? [], 'label' => trans('preferences.layout_edit_unused'), 'id' => 'unused'])
</div>
<div class="form-group text-right">
<a href="{{ url('/my-account/interface') }}" class="button outline">{{ trans('common.cancel') }}</a>
<button type="submit" class="button">{{ trans('preferences.layout_edit_save') }}</button>
@@ -0,0 +1,17 @@
{{--
$columnBlocks - array - Blocks to list
$label - string - Section title
$id - string - identifier for location/column
--}}
<div class="form-group flex">
<label id="layout-blocks-{{ $id }}">{{ $label }}</label>
<ul refs="layout-editor@column"
data-column="{{ $id }}"
aria-labelledby="layout-blocks-{{ $id }}"
class="scroll-box available-option-list">
<li class="text-muted empty-state px-m py-s italic text-small">{{ trans('preferences.layout_edit_empty') }}</li>
@foreach($columnBlocks as $block)
@include('settings.layouts.parts.block', ['block' => $block])
@endforeach
</ul>
</div>
@@ -0,0 +1,18 @@
{{--
$block - View Block instance to show
--}}
<li data-id="{{ $block->getLabel() }}"
class="scroll-box-item items-center">
<div class="handle px-s">@icon('grip')</div>
<div class="text-small">{{ $block->getLabel() }}</div>
<div class="buttons flex-container-row items-center ml-auto px-xxs py-xxs">
<button type="button" data-action="move_up" class="icon-button p-xxs"
title="{{ trans('entities.books_sort_move_up') }}">@icon('chevron-up')</button>
<button type="button" data-action="move_down" class="icon-button p-xxs"
title="{{ trans('entities.books_sort_move_down') }}">@icon('chevron-down')</button>
<button type="button" data-action="remove" class="icon-button p-xxs"
title="{{ trans('common.remove') }}">@icon('remove')</button>
<button type="button" data-action="add" class="icon-button p-xxs"
title="{{ trans('common.add') }}">@icon('add-small')</button>
</div>
</li>
+38
View File
@@ -0,0 +1,38 @@
<?php
namespace Tests\View;
use BookStack\View\ViewBlockDefaults;
use Tests\TestCase;
class ViewBlockDefaultsTest extends TestCase
{
public function test_each_has_a_unique_id_and_non_key_label()
{
$locations = ViewBlockDefaults::getLocations();
$classes = [];
$trueById = [];
foreach ($locations as $location) {
$blocksByPosition = ViewBlockDefaults::getForLocation($location);
foreach ($blocksByPosition as $blocks) {
array_push($classes, ...$blocks);
}
}
$blocks = array_unique($classes);
foreach ($blocks as $block) {
$instance = app()->make($block);
$id = $instance->getId();
$this->assertArrayNotHasKey($id, $trueById);
$trueById[$id] = true;
$label = $instance->getLabel();
$this->assertIsString($label);
$this->assertDoesNotMatchRegularExpression('/^[a-z]{1,10}\./', $label);
}
$this->assertGreaterThan(10, count($trueById));
}
}