]> BookStack Code Mirror - bookstack/commitdiff
Notifications: Updated watch control to show parent status
authorDan Brown <redacted>
Wed, 9 Aug 2023 13:53:31 +0000 (14:53 +0100)
committerDan Brown <redacted>
Wed, 9 Aug 2023 13:53:31 +0000 (14:53 +0100)
13 files changed:
app/Activity/Controllers/WatchController.php
app/Activity/Tools/UserEntityWatchOptions.php [new file with mode: 0644]
app/Activity/Tools/UserWatchOptions.php [deleted file]
app/Activity/Tools/WatchedParentDetails.php [new file with mode: 0644]
app/Entities/Controllers/BookController.php
app/Entities/Controllers/ChapterController.php
app/Entities/Controllers/PageController.php
lang/en/entities.php
resources/views/books/show.blade.php
resources/views/chapters/show.blade.php
resources/views/entities/meta.blade.php
resources/views/entities/watch-controls.blade.php
resources/views/pages/show.blade.php

index a297aaafc33d2fd63f60d7019af2fcb5248a892f..e0596864cd195e0cfa6bdf5d0e6424b7ede6f7ff 100644 (file)
@@ -3,7 +3,7 @@
 namespace BookStack\Activity\Controllers;
 
 use BookStack\Activity\Models\Watch;
-use BookStack\Activity\Tools\UserWatchOptions;
+use BookStack\Activity\Tools\UserEntityWatchOptions;
 use BookStack\App\Model;
 use BookStack\Entities\Models\Entity;
 use BookStack\Http\Controller;
@@ -20,8 +20,8 @@ class WatchController extends Controller
         ]);
 
         $watchable = $this->getValidatedModelFromRequest($request);
-        $watchOptions = new UserWatchOptions(user());
-        $watchOptions->updateEntityWatchLevel($watchable, $requestData['level']);
+        $watchOptions = new UserEntityWatchOptions(user(), $watchable);
+        $watchOptions->updateWatchLevel($requestData['level']);
 
         $this->showSuccessNotification(trans('activities.watch_update_level_notification'));
 
diff --git a/app/Activity/Tools/UserEntityWatchOptions.php b/app/Activity/Tools/UserEntityWatchOptions.php
new file mode 100644 (file)
index 0000000..26d8308
--- /dev/null
@@ -0,0 +1,124 @@
+<?php
+
+namespace BookStack\Activity\Tools;
+
+use BookStack\Activity\Models\Watch;
+use BookStack\Activity\WatchLevels;
+use BookStack\Entities\Models\BookChild;
+use BookStack\Entities\Models\Entity;
+use BookStack\Entities\Models\Page;
+use BookStack\Users\Models\User;
+use Illuminate\Database\Eloquent\Builder;
+
+class UserEntityWatchOptions
+{
+    protected ?array $watchMap = null;
+
+    public function __construct(
+        protected User $user,
+        protected Entity $entity,
+    ) {
+    }
+
+    public function canWatch(): bool
+    {
+        return $this->user->can('receive-notifications') && !$this->user->isDefault();
+    }
+
+    public function getWatchLevel(): string
+    {
+        return WatchLevels::levelValueToName($this->getWatchLevelValue());
+    }
+
+    public function isWatching(): bool
+    {
+        return $this->getWatchLevelValue() !== WatchLevels::DEFAULT;
+    }
+
+    public function getWatchedParent(): ?WatchedParentDetails
+    {
+        $watchMap = $this->getWatchMap();
+        unset($watchMap[$this->entity->getMorphClass()]);
+
+        if (isset($watchMap['chapter'])) {
+            return new WatchedParentDetails('chapter', $watchMap['chapter']);
+        }
+
+        if (isset($watchMap['book'])) {
+            return new WatchedParentDetails('book', $watchMap['book']);
+        }
+
+        return null;
+    }
+
+    public function updateWatchLevel(string $level): void
+    {
+        $levelValue = WatchLevels::levelNameToValue($level);
+        if ($levelValue < 0) {
+            $this->remove();
+            return;
+        }
+
+        $this->updateLevel($levelValue);
+    }
+
+    public function getWatchMap(): array
+    {
+        if (!is_null($this->watchMap)) {
+            return $this->watchMap;
+        }
+
+        $entities = [$this->entity];
+        if ($this->entity instanceof BookChild) {
+            $entities[] = $this->entity->book;
+        }
+        if ($this->entity instanceof Page && $this->entity->chapter) {
+            $entities[] = $this->entity->chapter;
+        }
+
+        $query = Watch::query()->where(function (Builder $subQuery) use ($entities) {
+            foreach ($entities as $entity) {
+                $subQuery->orWhere(function (Builder $whereQuery) use ($entity) {
+                    $whereQuery->where('watchable_type', '=', $entity->getMorphClass())
+                        ->where('watchable_id', '=', $entity->id);
+                });
+            }
+        });
+
+        $this->watchMap = $query->get(['watchable_type', 'level'])
+            ->pluck('level', 'watchable_type')
+            ->toArray();
+
+        return $this->watchMap;
+    }
+
+    protected function getWatchLevelValue()
+    {
+        return $this->getWatchMap()[$this->entity->getMorphClass()] ?? WatchLevels::DEFAULT;
+    }
+
+    protected function updateLevel(int $levelValue): void
+    {
+        Watch::query()->updateOrCreate([
+            'watchable_id' => $this->entity->id,
+            'watchable_type' => $this->entity->getMorphClass(),
+            'user_id' => $this->user->id,
+        ], [
+            'level' => $levelValue,
+        ]);
+        $this->watchMap = null;
+    }
+
+    protected function remove(): void
+    {
+        $this->entityQuery()->delete();
+        $this->watchMap = null;
+    }
+
+    protected function entityQuery(): Builder
+    {
+        return Watch::query()->where('watchable_id', '=', $this->entity->id)
+            ->where('watchable_type', '=', $this->entity->getMorphClass())
+            ->where('user_id', '=', $this->user->id);
+    }
+}
diff --git a/app/Activity/Tools/UserWatchOptions.php b/app/Activity/Tools/UserWatchOptions.php
deleted file mode 100644 (file)
index 64c5f31..0000000
+++ /dev/null
@@ -1,67 +0,0 @@
-<?php
-
-namespace BookStack\Activity\Tools;
-
-use BookStack\Activity\Models\Watch;
-use BookStack\Activity\WatchLevels;
-use BookStack\Entities\Models\Entity;
-use BookStack\Users\Models\User;
-use Illuminate\Database\Eloquent\Builder;
-
-class UserWatchOptions
-{
-    public function __construct(
-        protected User $user,
-    ) {
-    }
-
-    public function canWatch(): bool
-    {
-        return $this->user->can('receive-notifications') && !$this->user->isDefault();
-    }
-
-    public function getEntityWatchLevel(Entity $entity): string
-    {
-        $levelValue = $this->entityQuery($entity)->first(['level'])->level ?? -1;
-        return WatchLevels::levelValueToName($levelValue);
-    }
-
-    public function isWatching(Entity $entity): bool
-    {
-        return $this->entityQuery($entity)->exists();
-    }
-
-    public function updateEntityWatchLevel(Entity $entity, string $level): void
-    {
-        $levelValue = WatchLevels::levelNameToValue($level);
-        if ($levelValue < 0) {
-            $this->removeForEntity($entity);
-            return;
-        }
-
-        $this->updateForEntity($entity, $levelValue);
-    }
-
-    protected function updateForEntity(Entity $entity, int $levelValue): void
-    {
-        Watch::query()->updateOrCreate([
-            'watchable_id' => $entity->id,
-            'watchable_type' => $entity->getMorphClass(),
-            'user_id' => $this->user->id,
-        ], [
-            'level' => $levelValue,
-        ]);
-    }
-
-    protected function removeForEntity(Entity $entity): void
-    {
-        $this->entityQuery($entity)->delete();
-    }
-
-    protected function entityQuery(Entity $entity): Builder
-    {
-        return Watch::query()->where('watchable_id', '=', $entity->id)
-            ->where('watchable_type', '=', $entity->getMorphClass())
-            ->where('user_id', '=', $this->user->id);
-    }
-}
diff --git a/app/Activity/Tools/WatchedParentDetails.php b/app/Activity/Tools/WatchedParentDetails.php
new file mode 100644 (file)
index 0000000..5a881c0
--- /dev/null
@@ -0,0 +1,19 @@
+<?php
+
+namespace BookStack\Activity\Tools;
+
+use BookStack\Activity\WatchLevels;
+
+class WatchedParentDetails
+{
+    public function __construct(
+        public string $type,
+        public int $level,
+    ) {
+    }
+
+    public function ignoring(): bool
+    {
+        return $this->level === WatchLevels::IGNORE;
+    }
+}
index 3ce71c38aab998b6731d31d8e042fb37ce45f025..55d28c6847e32a1227cb4663902e3388fbb27524 100644 (file)
@@ -5,7 +5,7 @@ namespace BookStack\Entities\Controllers;
 use BookStack\Activity\ActivityQueries;
 use BookStack\Activity\ActivityType;
 use BookStack\Activity\Models\View;
-use BookStack\Activity\Tools\UserWatchOptions;
+use BookStack\Activity\Tools\UserEntityWatchOptions;
 use BookStack\Entities\Models\Bookshelf;
 use BookStack\Entities\Repos\BookRepo;
 use BookStack\Entities\Tools\BookContents;
@@ -139,7 +139,7 @@ class BookController extends Controller
             'current'           => $book,
             'bookChildren'      => $bookChildren,
             'bookParentShelves' => $bookParentShelves,
-            'watchOptions'      => new UserWatchOptions(user()),
+            'watchOptions'      => new UserEntityWatchOptions(user(), $book),
             'activity'          => $activities->entityActivity($book, 20, 1),
             'referenceCount'    => $this->referenceFetcher->getPageReferenceCountToEntity($book),
         ]);
index c28db38004d623f72fa823d39ba220f5c6344cfd..ee1df05819aae47c6e3a991480f0f891e7f3e4e7 100644 (file)
@@ -3,7 +3,7 @@
 namespace BookStack\Entities\Controllers;
 
 use BookStack\Activity\Models\View;
-use BookStack\Activity\Tools\UserWatchOptions;
+use BookStack\Activity\Tools\UserEntityWatchOptions;
 use BookStack\Entities\Models\Book;
 use BookStack\Entities\Repos\ChapterRepo;
 use BookStack\Entities\Tools\BookContents;
@@ -82,7 +82,7 @@ class ChapterController extends Controller
             'chapter'        => $chapter,
             'current'        => $chapter,
             'sidebarTree'    => $sidebarTree,
-            'watchOptions'   => new UserWatchOptions(user()),
+            'watchOptions'   => new UserEntityWatchOptions(user(), $chapter),
             'pages'          => $pages,
             'next'           => $nextPreviousLocator->getNext(),
             'previous'       => $nextPreviousLocator->getPrevious(),
index dad05d034a8bf2f754d4a6bfb9e51a6f425d84db..6249310650494d19c157bd6b56616d017fb12df1 100644 (file)
@@ -4,7 +4,7 @@ namespace BookStack\Entities\Controllers;
 
 use BookStack\Activity\Models\View;
 use BookStack\Activity\Tools\CommentTree;
-use BookStack\Activity\Tools\UserWatchOptions;
+use BookStack\Activity\Tools\UserEntityWatchOptions;
 use BookStack\Entities\Models\Page;
 use BookStack\Entities\Repos\PageRepo;
 use BookStack\Entities\Tools\BookContents;
@@ -152,7 +152,7 @@ class PageController extends Controller
             'sidebarTree'     => $sidebarTree,
             'commentTree'     => $commentTree,
             'pageNav'         => $pageNav,
-            'watchOptions'    => new UserWatchOptions(user()),
+            'watchOptions'    => new UserEntityWatchOptions(user(), $page),
             'next'            => $nextPreviousLocator->getNext(),
             'previous'        => $nextPreviousLocator->getPrevious(),
             'referenceCount'  => $this->referenceFetcher->getPageReferenceCountToEntity($page),
index 87c09634b749a5f6f296575027af7372ed4f7f86..f1e7dcc01b6a352bf609cffa4eb7a3e5d4f13bea 100644 (file)
@@ -421,4 +421,8 @@ return [
     'watch_detail_new' => 'Watching for new pages',
     'watch_detail_updates' => 'Watching new pages and updates',
     'watch_detail_comments' => 'Watching new pages, updates & comments',
+    'watch_detail_parent_book' => 'Watching via parent book',
+    'watch_detail_parent_book_ignore' => 'Ignoring via parent book',
+    'watch_detail_parent_chapter' => 'Watching via parent chapter',
+    'watch_detail_parent_chapter_ignore' => 'Ignoring via parent chapter',
 ];
index b52a103a4b9331dd7b794068e27e5f9d0a7ee32a..0fb98e304d3cb754e064e802038e65dab5132979 100644 (file)
 
             <hr class="primary-background">
 
-            @if($watchOptions->canWatch() && !$watchOptions->isWatching($book))
+            @if($watchOptions->canWatch() && !$watchOptions->isWatching())
                 @include('entities.watch-action', ['entity' => $book])
             @endif
             @if(signedInUser())
index 3b1cfb07c4b3c62ab56a451735a7e075d0efae7b..67a8b9fa544c646e1484ce75286f1fd6c9c804ac 100644 (file)
 
             <hr class="primary-background"/>
 
-            @if($watchOptions->canWatch() && !$watchOptions->isWatching($chapter))
+            @if($watchOptions->canWatch() && !$watchOptions->isWatching())
                 @include('entities.watch-action', ['entity' => $chapter])
             @endif
             @if(signedInUser())
index 6783902a1552cd49bafabf52a79892f42c7bc823..2298be8bb27689ec2435828f7457ff2048942b51 100644 (file)
         </a>
     @endif
 
-    @if($watchOptions?->canWatch() && $watchOptions->isWatching($entity))
-        @php
-            $watchLevel = $watchOptions->getEntityWatchLevel($entity);
-        @endphp
-        <div component="dropdown"
-             class="dropdown-container block my-xxs">
-            <a refs="dropdown@toggle" href="#" class="entity-meta-item my-none">
-                @icon(($watchLevel === 'ignore' ? 'watch-ignore' : 'watch'))
-                <span>{{ trans('entities.watch_detail_' . $watchLevel) }}</span>
-            </a>
-            @include('entities.watch-controls', ['entity' => $entity, 'watchLevel' => $watchLevel])
-        </div>
+    @if($watchOptions?->canWatch())
+        @if($watchOptions->isWatching())
+            @include('entities.watch-controls', [
+                'entity' => $entity,
+                'watchLevel' => $watchOptions->getWatchLevel(),
+                'label' => trans('entities.watch_detail_' . $watchOptions->getWatchLevel()),
+                'ignoring' => $watchOptions->getWatchLevel() === 'ignore',
+            ])
+        @elseif($watchedParent = $watchOptions->getWatchedParent())
+            @include('entities.watch-controls', [
+                'entity' => $entity,
+                'watchLevel' => $watchOptions->getWatchLevel(),
+                'label' => trans('entities.watch_detail_parent_' . $watchedParent->type . ($watchedParent->ignoring() ? '_ignore' : '')),
+                'ignoring' => $watchedParent->ignoring(),
+            ])
+        @endif
     @endif
 </div>
\ No newline at end of file
index 5ad6108a032fd3c80bdcc7f9c94fc7f0d0d9c88b..4fdda53476140053165fb8d38903057c15c61457 100644 (file)
@@ -1,35 +1,42 @@
-<form action="{{ url('/watching/update') }}" method="POST">
-    {{ method_field('PUT') }}
-    {{ csrf_field() }}
-    <input type="hidden" name="type" value="{{ get_class($entity) }}">
-    <input type="hidden" name="id" value="{{ $entity->id }}">
+<div component="dropdown"
+     class="dropdown-container block my-xxs">
+    <a refs="dropdown@toggle" href="#" class="entity-meta-item my-none">
+        @icon(($ignoring ? 'watch-ignore' : 'watch'))
+        <span>{{ $label }}</span>
+    </a>
+    <form action="{{ url('/watching/update') }}" method="POST">
+        {{ method_field('PUT') }}
+        {{ csrf_field() }}
+        <input type="hidden" name="type" value="{{ get_class($entity) }}">
+        <input type="hidden" name="id" value="{{ $entity->id }}">
 
-    <ul refs="dropdown@menu" class="dropdown-menu xl-limited anchor-left pb-none">
-        @foreach(\BookStack\Activity\WatchLevels::all() as $option => $value)
-            <li>
-                <button name="level" value="{{ $option }}" class="icon-item">
-                    @if($watchLevel === $option)
-                        <span class="text-pos pt-m"
-                              title="{{ trans('common.status_active') }}">@icon('check-circle')</span>
-                    @else
-                        <span title="{{ trans('common.status_inactive') }}"></span>
-                    @endif
-                    <div class="break-text">
-                        <div class="mb-xxs"><strong>{{ trans('entities.watch_title_' . $option) }}</strong></div>
-                        <div class="text-muted text-small">
-                            {{ trans('entities.watch_desc_' . $option) }}
+        <ul refs="dropdown@menu" class="dropdown-menu xl-limited anchor-left pb-none">
+            @foreach(\BookStack\Activity\WatchLevels::all() as $option => $value)
+                <li>
+                    <button name="level" value="{{ $option }}" class="icon-item">
+                        @if($watchLevel === $option)
+                            <span class="text-pos pt-m"
+                                  title="{{ trans('common.status_active') }}">@icon('check-circle')</span>
+                        @else
+                            <span title="{{ trans('common.status_inactive') }}"></span>
+                        @endif
+                        <div class="break-text">
+                            <div class="mb-xxs"><strong>{{ trans('entities.watch_title_' . $option) }}</strong></div>
+                            <div class="text-muted text-small">
+                                {{ trans('entities.watch_desc_' . $option) }}
+                            </div>
                         </div>
-                    </div>
-                </button>
-            </li>
+                    </button>
+                </li>
+                <li>
+                    <hr class="my-none">
+                </li>
+            @endforeach
             <li>
-                <hr class="my-none">
+                <a href="{{ url('/preferences/notifications') }}"
+                   target="_blank"
+                   class="text-item text-muted text-small break-text">{{ trans('entities.watch_change_default') }}</a>
             </li>
-        @endforeach
-        <li>
-            <a href="{{ url('/preferences/notifications') }}"
-               target="_blank"
-               class="text-item text-muted text-small break-text">{{ trans('entities.watch_change_default') }}</a>
-        </li>
-    </ul>
-</form>
\ No newline at end of file
+        </ul>
+    </form>
+</div>
\ No newline at end of file
index aefcb96e1105e810a41dd1da407b302409ab56ca..7d44f677d0720c5a90a78e74309b1c80c0e86329 100644 (file)
 
             <hr class="primary-background"/>
 
-            @if($watchOptions->canWatch() && !$watchOptions->isWatching($page))
+            @if($watchOptions->canWatch() && !$watchOptions->isWatching())
                 @include('entities.watch-action', ['entity' => $page])
             @endif
             @if(signedInUser())