Notifications: Updated watch control to show parent status

This commit is contained in:
Dan Brown 2023-08-09 14:53:31 +01:00
parent ecab2c8e42
commit c47b3f805a
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
13 changed files with 213 additions and 122 deletions

View 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'));

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View 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),
]);

View 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(),

View 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),

View 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',
];

View File

@ -139,7 +139,7 @@
<hr class="primary-background">
@if($watchOptions->canWatch() && !$watchOptions->isWatching($book))
@if($watchOptions->canWatch() && !$watchOptions->isWatching())
@include('entities.watch-action', ['entity' => $book])
@endif
@if(signedInUser())

View File

@ -157,7 +157,7 @@
<hr class="primary-background"/>
@if($watchOptions->canWatch() && !$watchOptions->isWatching($chapter))
@if($watchOptions->canWatch() && !$watchOptions->isWatching())
@include('entities.watch-action', ['entity' => $chapter])
@endif
@if(signedInUser())

View File

@ -69,17 +69,21 @@
</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>

View 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>
</ul>
</form>
</div>

View File

@ -185,7 +185,7 @@
<hr class="primary-background"/>
@if($watchOptions->canWatch() && !$watchOptions->isWatching($page))
@if($watchOptions->canWatch() && !$watchOptions->isWatching())
@include('entities.watch-action', ['entity' => $page])
@endif
@if(signedInUser())