]> BookStack Code Mirror - bookstack/commitdiff
Reviewed and refactored next/previous navigation button implementation
authorDan Brown <redacted>
Sat, 29 May 2021 11:39:41 +0000 (12:39 +0100)
committerDan Brown <redacted>
Sat, 29 May 2021 11:39:41 +0000 (12:39 +0100)
- Updated styling to include item name.
- Extracted used text to translations.
- Updated the design to better suit the surrounding blocks.
- Removed newly added model/repo methods.
- Moved core logic out of controller and instead into a "NextPreviousContentLocator"
helper with re-uses the output from the book-tree generation.
- Also added the system to chapters.

For #2511

13 files changed:
app/Entities/Models/Page.php
app/Entities/Repos/PageRepo.php
app/Entities/Tools/NextPreviousContentLocator.php [new file with mode: 0644]
app/Http/Controllers/ChapterController.php
app/Http/Controllers/PageController.php
resources/lang/en/common.php
resources/sass/_blocks.scss
resources/sass/_layout.scss
resources/sass/_lists.scss
resources/sass/_text.scss
resources/views/chapters/show.blade.php
resources/views/pages/show.blade.php
resources/views/partials/entity-sibling-navigation.blade.php [new file with mode: 0644]

index 888a0db33b09445afb5e5639322190d6dd903e1b..93fb218932cf6e9f2796fce7f3aaac8586c43490 100644 (file)
@@ -138,13 +138,4 @@ class Page extends BookChild
         $refreshed->html = (new PageContent($refreshed))->render();
         return $refreshed;
     }
-    /**
-     * Get the parent chapter ID.
-     */
-    public function getParentChapter()
-    {
-        $chapterId = $this->chapter()->visible()
-        ->get('id');
-        return $chapterId;
-    }
 }
index 651548885a815fca2c7853974f5f27926b8d2be8..5eb882a026a3f803369b56b06b6334c46e8773c9 100644 (file)
@@ -468,10 +468,4 @@ class PageRepo
             ->where('page_id', '=', $page->id)
             ->orderBy('created_at', 'desc');
     }
-    /**
-     * Get page details by chapter ID.
-     */
-    public function getPageByChapterID(int $id){
-        return Page::visible()->where('chapter_id', '=', $id)->get(['id','slug']);
-    }
 }
diff --git a/app/Entities/Tools/NextPreviousContentLocator.php b/app/Entities/Tools/NextPreviousContentLocator.php
new file mode 100644 (file)
index 0000000..bfb0f4a
--- /dev/null
@@ -0,0 +1,69 @@
+<?php namespace BookStack\Entities\Tools;
+
+use BookStack\Entities\Models\BookChild;
+use BookStack\Entities\Models\Entity;
+use Illuminate\Support\Collection;
+
+/**
+ * Finds the next or previous content of a book element (page or chapter).
+ */
+class NextPreviousContentLocator
+{
+    protected $relativeBookItem;
+    protected $flatTree;
+    protected $currentIndex = null;
+
+    /**
+     * NextPreviousContentLocator constructor.
+     */
+    public function __construct(BookChild $relativeBookItem, Collection $bookTree)
+    {
+        $this->relativeBookItem = $relativeBookItem;
+        $this->flatTree = $this->treeToFlatOrderedCollection($bookTree);
+        $this->currentIndex = $this->getCurrentIndex();
+    }
+
+    /**
+     * Get the next logical entity within the book hierarchy.
+     */
+    public function getNext(): ?Entity
+    {
+        return $this->flatTree->get($this->currentIndex + 1);
+    }
+
+    /**
+     * Get the next logical entity within the book hierarchy.
+     */
+    public function getPrevious(): ?Entity
+    {
+        return $this->flatTree->get($this->currentIndex - 1);
+    }
+
+    /**
+     * Get the index of the current relative item.
+     */
+    protected function getCurrentIndex(): ?int
+    {
+        $index = $this->flatTree->search(function (Entity $entity) {
+            return get_class($entity) === get_class($this->relativeBookItem)
+                && $entity->id === $this->relativeBookItem->id;
+        });
+        return $index === false ? null : $index;
+    }
+
+    /**
+     * Convert a book tree collection to a flattened version
+     * where all items follow the expected order of user flow.
+     */
+    protected function treeToFlatOrderedCollection(Collection $bookTree): Collection
+    {
+        $flatOrdered = collect();
+        /** @var Entity $item */
+        foreach ($bookTree->all() as $item) {
+            $flatOrdered->push($item);
+            $childPages = $item->visible_pages ?? [];
+            $flatOrdered = $flatOrdered->concat($childPages);
+        }
+        return $flatOrdered;
+    }
+}
index fbef815821e3bcaacf15cd34b38b4594778f1739..1c968a82c251d7d864e02ad8cd572ff5e160bbca 100644 (file)
@@ -4,6 +4,7 @@ use BookStack\Actions\View;
 use BookStack\Entities\Models\Book;
 use BookStack\Entities\Tools\BookContents;
 use BookStack\Entities\Repos\ChapterRepo;
+use BookStack\Entities\Tools\NextPreviousContentLocator;
 use BookStack\Entities\Tools\PermissionsUpdater;
 use BookStack\Exceptions\MoveOperationException;
 use BookStack\Exceptions\NotFoundException;
@@ -65,6 +66,7 @@ class ChapterController extends Controller
 
         $sidebarTree = (new BookContents($chapter->book))->getTree();
         $pages = $chapter->getVisiblePages();
+        $nextPreviousLocator = new NextPreviousContentLocator($chapter, $sidebarTree);
         View::incrementFor($chapter);
 
         $this->setPageTitle($chapter->getShortName());
@@ -73,7 +75,9 @@ class ChapterController extends Controller
             'chapter' => $chapter,
             'current' => $chapter,
             'sidebarTree' => $sidebarTree,
-            'pages' => $pages
+            'pages' => $pages,
+            'next' => $nextPreviousLocator->getNext(),
+            'previous' => $nextPreviousLocator->getPrevious(),
         ]);
     }
 
index 616970a5d05758088d04e2ec034c46dda3766913..f76f0081070ae482dd5be4ac857ffa221784fc10 100644 (file)
@@ -2,6 +2,7 @@
 
 use BookStack\Actions\View;
 use BookStack\Entities\Tools\BookContents;
+use BookStack\Entities\Tools\NextPreviousContentLocator;
 use BookStack\Entities\Tools\PageContent;
 use BookStack\Entities\Tools\PageEditActivity;
 use BookStack\Entities\Models\Page;
@@ -142,39 +143,8 @@ class PageController extends Controller
             $page->load(['comments.createdBy']);
         }
 
-        $chapterId = $page->getParentChapter();
-        $allPageSlugs = $this->pageRepo->getPageByChapterID($chapterId[0]->id);
-        $pos = 0;
-        foreach ($allPageSlugs as $slug){
-            if($pageSlug === $slug->slug){
-                $currPagePos = $pos;
-            }
-            $pos++;
-            $pageUrl = $this->pageRepo->getBySlug($bookSlug, $slug->slug);
-            $urlLink[] = $pageUrl->getUrl();
-        }
-        for($i=0; $i <= $currPagePos; $i++){
-            $nextCount = $i+1;
-            $prevCount = $i-1;
-            $prevPage = '#';
-            $nextPage = '#';
-            if($nextCount < count($urlLink)){
-                $nextPage = $urlLink[$nextCount];
-            }
-            if($currPagePos == $i && $currPagePos != 0){
-                $prevPage = $urlLink[$prevCount];    
-            }
-        }
+        $nextPreviousLocator = new NextPreviousContentLocator($page, $sidebarTree);
 
-        $disablePrev = "";
-        $disableNxt = "";
-        if($prevPage == "#"){
-            $disablePrev = "disabled";
-        }
-        if($nextPage == "#"){
-            $disableNxt = "disabled";
-        }
-        
         View::incrementFor($page);
         $this->setPageTitle($page->getShortName());
         return view('pages.show', [
@@ -184,10 +154,8 @@ class PageController extends Controller
             'sidebarTree' => $sidebarTree,
             'commentsEnabled' => $commentsEnabled,
             'pageNav' => $pageNav,
-            'prevPage' => $prevPage,
-            'nextPage' => $nextPage,
-            'disablePrev' => $disablePrev,
-            'disableNxt' => $disableNxt
+            'next' => $nextPreviousLocator->getNext(),
+            'previous' => $nextPreviousLocator->getPrevious(),
         ]);
     }
 
@@ -280,8 +248,8 @@ class PageController extends Controller
 
         $updateTime = $draft->updated_at->timestamp;
         return response()->json([
-            'status'    => 'success',
-            'message'   => trans('entities.pages_edit_draft_save_at'),
+            'status' => 'success',
+            'message' => trans('entities.pages_edit_draft_save_at'),
             'timestamp' => $updateTime
         ]);
     }
@@ -304,7 +272,7 @@ class PageController extends Controller
     {
         $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
         $this->checkOwnablePermission('page-delete', $page);
-        $this->setPageTitle(trans('entities.pages_delete_named', ['pageName'=>$page->getShortName()]));
+        $this->setPageTitle(trans('entities.pages_delete_named', ['pageName' => $page->getShortName()]));
         return view('pages.delete', [
             'book' => $page->book,
             'page' => $page,
@@ -320,7 +288,7 @@ class PageController extends Controller
     {
         $page = $this->pageRepo->getById($pageId);
         $this->checkOwnablePermission('page-update', $page);
-        $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName'=>$page->getShortName()]));
+        $this->setPageTitle(trans('entities.pages_delete_draft_named', ['pageName' => $page->getShortName()]));
         return view('pages.delete', [
             'book' => $page->book,
             'page' => $page,
@@ -415,7 +383,7 @@ class PageController extends Controller
         try {
             $parent = $this->pageRepo->move($page, $entitySelection);
         } catch (Exception $exception) {
-            if ($exception instanceof  PermissionsException) {
+            if ($exception instanceof PermissionsException) {
                 $this->showPermissionError();
             }
 
@@ -459,7 +427,7 @@ class PageController extends Controller
         try {
             $pageCopy = $this->pageRepo->copy($page, $entitySelection, $newName);
         } catch (Exception $exception) {
-            if ($exception instanceof  PermissionsException) {
+            if ($exception instanceof PermissionsException) {
                 $this->showPermissionError();
             }
 
@@ -480,7 +448,7 @@ class PageController extends Controller
         $page = $this->pageRepo->getBySlug($bookSlug, $pageSlug);
         $this->checkOwnablePermission('restrictions-manage', $page);
         return view('pages.permissions', [
-            'page'  => $page,
+            'page' => $page,
         ]);
     }
 
index e198878ad30cb0b416e448911ee2c7f527611e96..d4508c3c7d4c9a7def3b457b1d9d2d96673d7d23 100644 (file)
@@ -42,6 +42,8 @@ return [
     'fullscreen' => 'Fullscreen',
     'favourite' => 'Favourite',
     'unfavourite' => 'Unfavourite',
+    'next' => 'Next',
+    'previous' => 'Previous',
 
     // Sort Options
     'sort_options' => 'Sort Options',
index 1ee4e070983744e2b848d7c6eee63f08ae12a0d0..f9c2061547fbdf5daf598d6ca75b7973d72fabfc 100644 (file)
   padding: $-m $-xxl;
   margin-inline-start: auto;
   margin-inline-end: auto;
-  margin-bottom: $-xl;
+  margin-bottom: $-l;
   overflow: initial;
   min-height: 60vh;
   &.auto-height {
   }
 }
 
+.outline-hover {
+  border: 1px solid transparent !important;
+  &:hover {
+    border: 1px solid rgba(0, 0, 0, 0.1) !important;
+  }
+}
+
+.fade-in-when-active {
+  opacity: 0.6;
+  transition: opacity ease-in-out 120ms;
+  &:hover, &:focus-within {
+    opacity: 1;
+  }
+}
+
 /**
  * Tags
  */
index c5af303f8751738b444e8beafdbb24f480e95d81..516d7d612e590d7a6ec516a689cdd102ff6606c7 100644 (file)
@@ -62,9 +62,6 @@
 }
 
 @include smaller-than($m) {
-  .grid.third.prev-next:not(.no-break) {
-    grid-template-columns: 1fr 1fr 1fr;
-  }
   .grid.third:not(.no-break) {
     grid-template-columns: 1fr 1fr;
   }
   .grid.right-focus.reverse-collapse > *:nth-child(1) {
     order: 1;
   }
-  .grid.third:not(.no-break) .text-m-left {
-    margin-left: 20%;
-  }
-  .grid.third:not(.no-break) .text-m-right {
-    margin-left: 45%;
-  }
 }
 
 @include smaller-than($s) {
   .grid.third:not(.no-break) {
     grid-template-columns: 1fr;
   }
-  .grid.third:not(.no-break) .text-m-left {
-    margin-left: 18%;
-  }
-  .grid.third:not(.no-break) .text-m-right {
-    margin-left: 20%;
-  }
 }
 
 @include smaller-than($xs) {
@@ -227,6 +212,13 @@ body.flexbox {
   }
 }
 
+/**
+ * Border radiuses
+ */
+.rounded {
+  border-radius: 4px;
+}
+
 /**
  * Inline content columns
  */
@@ -381,8 +373,4 @@ body.flexbox {
     margin-inline-start: 0;
     margin-inline-end: 0;
   }
-}
-
-.prev-next-btn {
-  height: 50px;
-}
+}
\ No newline at end of file
index 0bef608a7e2d4c9b0f925414b9ebeb7e895024ea..436c7e5333c359dd6cd1e2cb959d9013ca8a154c 100644 (file)
@@ -441,12 +441,8 @@ ul.pagination {
     background-color: rgba(0, 0, 0, 0.1);
     border-radius: 4px;
   }
-  &.outline-hover {
-    border: 1px solid transparent;
-  }
   &.outline-hover:hover {
     background-color: transparent;
-    border-color: rgba(0, 0, 0, 0.1);
   }
   &:focus {
     @include lightDark(background-color, #eee, #222);
index 4ece0ea201e8f4031b1c5d908b700f636abc1219..7a0987c66c1a1f1c87d71d210e32eb698345270a 100644 (file)
@@ -112,10 +112,11 @@ a {
   }
 }
 
-a.disabled {
-  pointer-events: none;
-  cursor: default;
-  opacity: 0.6;
+a.no-link-style {
+  color: inherit;
+  &:hover {
+    text-decoration: none;
+  }
 }
 
 .blended-links a {
@@ -141,6 +142,9 @@ hr {
   &.faded {
     background-image: linear-gradient(to right, #FFF, #e3e0e0 20%, #e3e0e0 80%, #FFF);
   }
+  &.darker {
+    @include lightDark(background, #DDD, #666);
+  }
   &.margin-top, &.even {
     margin-top: $-l;
   }
index 8aa3be1112b21c4984ea93956a0779b29dd90964..803536e9facda9a0dde17550bc709371e7efd0e6 100644 (file)
@@ -52,6 +52,8 @@
         @include('partials.entity-search-results')
     </main>
 
+    @include('partials.entity-sibling-navigation', ['next' => $next, 'previous' => $previous])
+
 @stop
 
 @section('right')
index 3476f3a3d732815502c2d93aba8f20c560adc2e0..6e84c44a86d8768f3c224308b95a67b0a7feae72 100644 (file)
             @include('pages.page-display')
         </div>
     </main>
-       
-    <div class="prev-next-btn">
-        <div class="grid third no-row-gap prev-next">
-            <div class="text-m-left">
-                <a class="{{ $disablePrev }}" href="{{ $prevPage }}">Previous Page</a>
-            </div>
-            <div></div>
-            <div class="text-m-right">
-                <a class="{{ $disableNxt }}" href="{{ $nextPage }}">Next Page</a>
-            </div>
-        </div>
-    </div>
+
+    @include('partials.entity-sibling-navigation', ['next' => $next, 'previous' => $previous])
 
     @if ($commentsEnabled)
-        <div class="container small p-none comments-container mb-l print-hidden">
+        @if(($previous || $next))
+            <div class="px-xl">
+                <hr class="darker">
+            </div>
+        @endif
+
+        <div class="px-xl comments-container mb-l print-hidden">
             @include('comments.comments', ['page' => $page])
             <div class="clearfix"></div>
         </div>
diff --git a/resources/views/partials/entity-sibling-navigation.blade.php b/resources/views/partials/entity-sibling-navigation.blade.php
new file mode 100644 (file)
index 0000000..7743081
--- /dev/null
@@ -0,0 +1,28 @@
+<div class="grid half collapse-xs items-center mb-m px-m no-row-gap fade-in-when-active print-hidden">
+    <div>
+        @if($previous)
+            <a href="{{  $previous->getUrl()  }}" class="outline-hover no-link-style block rounded">
+                <div class="px-m pt-xs text-muted">{{ trans('common.previous') }}</div>
+                <div class="inline-block">
+                    <div class="icon-list-item no-hover">
+                        <span class="text-{{ $previous->getType() }} ">@icon($previous->getType())</span>
+                        <span>{{ $previous->getShortName(48) }}</span>
+                    </div>
+                </div>
+            </a>
+        @endif
+    </div>
+    <div>
+        @if($next)
+            <a href="{{  $next->getUrl()  }}" class="outline-hover no-link-style block rounded text-xs-right">
+                <div class="px-m pt-xs text-muted text-xs-right">{{ trans('common.next') }}</div>
+                <div class="inline block">
+                    <div class="icon-list-item no-hover">
+                        <span class="text-{{ $next->getType() }} ">@icon($next->getType())</span>
+                        <span>{{ $next->getShortName(48) }}</span>
+                    </div>
+                </div>
+            </a>
+        @endif
+    </div>
+</div>
\ No newline at end of file