Notifications: Got core notification logic working for new pages
Also rolled out watch UI to chapter and page views
This commit is contained in:
parent
9779c1a357
commit
18ae67a138
|
@ -3,17 +3,20 @@
|
||||||
namespace BookStack\Activity\Notifications\Handlers;
|
namespace BookStack\Activity\Notifications\Handlers;
|
||||||
|
|
||||||
use BookStack\Activity\Models\Loggable;
|
use BookStack\Activity\Models\Loggable;
|
||||||
use BookStack\Activity\Models\Watch;
|
use BookStack\Activity\Notifications\Messages\PageCreationNotification;
|
||||||
use BookStack\Activity\Tools\EntityWatchers;
|
use BookStack\Activity\Tools\EntityWatchers;
|
||||||
use BookStack\Activity\WatchLevels;
|
use BookStack\Activity\WatchLevels;
|
||||||
|
use BookStack\Entities\Models\Page;
|
||||||
|
use BookStack\Permissions\PermissionApplicator;
|
||||||
use BookStack\Users\Models\User;
|
use BookStack\Users\Models\User;
|
||||||
|
|
||||||
class PageCreationNotificationHandler implements NotificationHandler
|
class PageCreationNotificationHandler implements NotificationHandler
|
||||||
{
|
{
|
||||||
public function handle(string $activityType, Loggable|string $detail, User $user): void
|
public function handle(string $activityType, Loggable|string $detail, User $user): void
|
||||||
{
|
{
|
||||||
// TODO
|
if (!($detail instanceof Page)) {
|
||||||
|
throw new \InvalidArgumentException("Detail for page create notifications must be a page");
|
||||||
|
}
|
||||||
// No user-level preferences to care about here.
|
// No user-level preferences to care about here.
|
||||||
// Possible Scenarios:
|
// Possible Scenarios:
|
||||||
// ✅ User watching parent chapter
|
// ✅ User watching parent chapter
|
||||||
|
@ -27,8 +30,15 @@ class PageCreationNotificationHandler implements NotificationHandler
|
||||||
|
|
||||||
// Get all relevant watchers
|
// Get all relevant watchers
|
||||||
$watchers = new EntityWatchers($detail, WatchLevels::NEW);
|
$watchers = new EntityWatchers($detail, WatchLevels::NEW);
|
||||||
|
$users = User::query()->whereIn('id', $watchers->getWatcherUserIds())->get();
|
||||||
|
|
||||||
// TODO - need to check entity visibility and receive-notifications permissions.
|
// TODO - Clean this up, likely abstract to base class
|
||||||
// Maybe abstract this to a generic late-stage filter?
|
// TODO - Prevent sending to current user
|
||||||
|
$permissions = app()->make(PermissionApplicator::class);
|
||||||
|
foreach ($users as $user) {
|
||||||
|
if ($user->can('receive-notifications') && $permissions->checkOwnableUserAccess($detail, 'view')) {
|
||||||
|
$user->notify(new PageCreationNotification($detail, $user));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,14 @@ use Illuminate\Database\Eloquent\Builder;
|
||||||
|
|
||||||
class EntityWatchers
|
class EntityWatchers
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* @var int[]
|
||||||
|
*/
|
||||||
protected array $watchers = [];
|
protected array $watchers = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var int[]
|
||||||
|
*/
|
||||||
protected array $ignorers = [];
|
protected array $ignorers = [];
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
|
@ -20,16 +27,35 @@ class EntityWatchers
|
||||||
$this->build();
|
$this->build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getWatcherUserIds(): array
|
||||||
|
{
|
||||||
|
return $this->watchers;
|
||||||
|
}
|
||||||
|
|
||||||
protected function build(): void
|
protected function build(): void
|
||||||
{
|
{
|
||||||
$watches = $this->getRelevantWatches();
|
$watches = $this->getRelevantWatches();
|
||||||
|
|
||||||
// TODO - De-dupe down watches per-user across entity types
|
// Sort before de-duping, so that the order looped below follows book -> chapter -> page ordering
|
||||||
// so we end up with [user_id => status] values
|
usort($watches, function (Watch $watchA, Watch $watchB) {
|
||||||
// then filter to current watch level, considering ignores,
|
$entityTypeDiff = $watchA->watchable_type <=> $watchB->watchable_type;
|
||||||
// then populate the class watchers/ignores with ids.
|
return $entityTypeDiff === 0 ? ($watchA->user_id <=> $watchB->user_id) : $entityTypeDiff;
|
||||||
|
});
|
||||||
|
|
||||||
|
// De-dupe by user id to get their most relevant level
|
||||||
|
$levelByUserId = [];
|
||||||
|
foreach ($watches as $watch) {
|
||||||
|
$levelByUserId[$watch->user_id] = $watch->level;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the class arrays
|
||||||
|
$this->watchers = array_keys(array_filter($levelByUserId, fn(int $level) => $level >= $this->watchLevel));
|
||||||
|
$this->ignorers = array_keys(array_filter($levelByUserId, fn(int $level) => $level === 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Watch[]
|
||||||
|
*/
|
||||||
protected function getRelevantWatches(): array
|
protected function getRelevantWatches(): array
|
||||||
{
|
{
|
||||||
/** @var Entity[] $entitiesInvolved */
|
/** @var Entity[] $entitiesInvolved */
|
||||||
|
@ -49,7 +75,7 @@ class EntityWatchers
|
||||||
});
|
});
|
||||||
|
|
||||||
return $query->get([
|
return $query->get([
|
||||||
'level', 'watchable_id', 'watchable_type', 'user_id'
|
'level', 'watchable_id', 'watchable_type', 'user_id'
|
||||||
])->all();
|
])->all();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
namespace BookStack\Entities\Controllers;
|
namespace BookStack\Entities\Controllers;
|
||||||
|
|
||||||
use BookStack\Activity\Models\View;
|
use BookStack\Activity\Models\View;
|
||||||
|
use BookStack\Activity\Tools\UserWatchOptions;
|
||||||
use BookStack\Entities\Models\Book;
|
use BookStack\Entities\Models\Book;
|
||||||
use BookStack\Entities\Repos\ChapterRepo;
|
use BookStack\Entities\Repos\ChapterRepo;
|
||||||
use BookStack\Entities\Tools\BookContents;
|
use BookStack\Entities\Tools\BookContents;
|
||||||
|
@ -81,6 +82,7 @@ class ChapterController extends Controller
|
||||||
'chapter' => $chapter,
|
'chapter' => $chapter,
|
||||||
'current' => $chapter,
|
'current' => $chapter,
|
||||||
'sidebarTree' => $sidebarTree,
|
'sidebarTree' => $sidebarTree,
|
||||||
|
'watchOptions' => new UserWatchOptions(user()),
|
||||||
'pages' => $pages,
|
'pages' => $pages,
|
||||||
'next' => $nextPreviousLocator->getNext(),
|
'next' => $nextPreviousLocator->getNext(),
|
||||||
'previous' => $nextPreviousLocator->getPrevious(),
|
'previous' => $nextPreviousLocator->getPrevious(),
|
||||||
|
|
|
@ -4,6 +4,7 @@ namespace BookStack\Entities\Controllers;
|
||||||
|
|
||||||
use BookStack\Activity\Models\View;
|
use BookStack\Activity\Models\View;
|
||||||
use BookStack\Activity\Tools\CommentTree;
|
use BookStack\Activity\Tools\CommentTree;
|
||||||
|
use BookStack\Activity\Tools\UserWatchOptions;
|
||||||
use BookStack\Entities\Models\Page;
|
use BookStack\Entities\Models\Page;
|
||||||
use BookStack\Entities\Repos\PageRepo;
|
use BookStack\Entities\Repos\PageRepo;
|
||||||
use BookStack\Entities\Tools\BookContents;
|
use BookStack\Entities\Tools\BookContents;
|
||||||
|
@ -151,6 +152,7 @@ class PageController extends Controller
|
||||||
'sidebarTree' => $sidebarTree,
|
'sidebarTree' => $sidebarTree,
|
||||||
'commentTree' => $commentTree,
|
'commentTree' => $commentTree,
|
||||||
'pageNav' => $pageNav,
|
'pageNav' => $pageNav,
|
||||||
|
'watchOptions' => new UserWatchOptions(user()),
|
||||||
'next' => $nextPreviousLocator->getNext(),
|
'next' => $nextPreviousLocator->getNext(),
|
||||||
'previous' => $nextPreviousLocator->getPrevious(),
|
'previous' => $nextPreviousLocator->getPrevious(),
|
||||||
'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($page),
|
'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($page),
|
||||||
|
|
|
@ -139,12 +139,12 @@
|
||||||
|
|
||||||
<hr class="primary-background">
|
<hr class="primary-background">
|
||||||
|
|
||||||
@if(signedInUser())
|
|
||||||
@include('entities.favourite-action', ['entity' => $book])
|
|
||||||
@endif
|
|
||||||
@if($watchOptions->canWatch() && !$watchOptions->isWatching($book))
|
@if($watchOptions->canWatch() && !$watchOptions->isWatching($book))
|
||||||
@include('entities.watch-action', ['entity' => $book])
|
@include('entities.watch-action', ['entity' => $book])
|
||||||
@endif
|
@endif
|
||||||
|
@if(signedInUser())
|
||||||
|
@include('entities.favourite-action', ['entity' => $book])
|
||||||
|
@endif
|
||||||
@if(userCan('content-export'))
|
@if(userCan('content-export'))
|
||||||
@include('entities.export-menu', ['entity' => $book])
|
@include('entities.export-menu', ['entity' => $book])
|
||||||
@endif
|
@endif
|
||||||
|
|
|
@ -157,6 +157,9 @@
|
||||||
|
|
||||||
<hr class="primary-background"/>
|
<hr class="primary-background"/>
|
||||||
|
|
||||||
|
@if($watchOptions->canWatch() && !$watchOptions->isWatching($chapter))
|
||||||
|
@include('entities.watch-action', ['entity' => $chapter])
|
||||||
|
@endif
|
||||||
@if(signedInUser())
|
@if(signedInUser())
|
||||||
@include('entities.favourite-action', ['entity' => $chapter])
|
@include('entities.favourite-action', ['entity' => $chapter])
|
||||||
@endif
|
@endif
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<input type="hidden" name="id" value="{{ $entity->id }}">
|
<input type="hidden" name="id" value="{{ $entity->id }}">
|
||||||
|
|
||||||
<ul refs="dropdown@menu" class="dropdown-menu xl-limited anchor-left pb-none">
|
<ul refs="dropdown@menu" class="dropdown-menu xl-limited anchor-left pb-none">
|
||||||
@foreach(\BookStack\Activity\WatchLevels::all() as $option)
|
@foreach(\BookStack\Activity\WatchLevels::all() as $option => $value)
|
||||||
<li>
|
<li>
|
||||||
<button name="level" value="{{ $option }}" class="icon-item">
|
<button name="level" value="{{ $option }}" class="icon-item">
|
||||||
@if($watchLevel === $option)
|
@if($watchLevel === $option)
|
||||||
|
|
|
@ -185,6 +185,9 @@
|
||||||
|
|
||||||
<hr class="primary-background"/>
|
<hr class="primary-background"/>
|
||||||
|
|
||||||
|
@if($watchOptions->canWatch() && !$watchOptions->isWatching($page))
|
||||||
|
@include('entities.watch-action', ['entity' => $page])
|
||||||
|
@endif
|
||||||
@if(signedInUser())
|
@if(signedInUser())
|
||||||
@include('entities.favourite-action', ['entity' => $page])
|
@include('entities.favourite-action', ['entity' => $page])
|
||||||
@endif
|
@endif
|
||||||
|
|
Loading…
Reference in New Issue