Notifications: Linked watch functionality to UI
Got watch system working to an initial base state. Moved some existing logic where it makes sense.
This commit is contained in:
parent
8cdf3203ef
commit
9d149e4d36
13 changed files with 161 additions and 56 deletions
|
@ -3,6 +3,7 @@
|
|||
namespace BookStack\Activity\Controllers;
|
||||
|
||||
use BookStack\Activity\Models\Watch;
|
||||
use BookStack\Activity\Tools\UserWatchOptions;
|
||||
use BookStack\App\Model;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Http\Controller;
|
||||
|
@ -19,13 +20,12 @@ class WatchController extends Controller
|
|||
]);
|
||||
|
||||
$watchable = $this->getValidatedModelFromRequest($request);
|
||||
$newLevel = Watch::optionNameToLevel($requestData['level']);
|
||||
$watchOptions = new UserWatchOptions(user());
|
||||
$watchOptions->updateEntityWatchLevel($watchable, $requestData['level']);
|
||||
|
||||
if ($newLevel < 0) {
|
||||
// TODO - Delete
|
||||
} else {
|
||||
// TODO - Upsert
|
||||
}
|
||||
$this->showSuccessNotification(trans('activities.watch_update_level_notification'));
|
||||
|
||||
return redirect()->back();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -18,13 +18,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
|||
*/
|
||||
class Watch extends Model
|
||||
{
|
||||
protected static array $levelByOption = [
|
||||
'default' => -1,
|
||||
'ignore' => 0,
|
||||
'new' => 1,
|
||||
'updates' => 2,
|
||||
'comments' => 3,
|
||||
];
|
||||
protected $guarded = [];
|
||||
|
||||
public function watchable()
|
||||
{
|
||||
|
@ -36,17 +30,4 @@ class Watch extends Model
|
|||
return $this->hasMany(JointPermission::class, 'entity_id', 'watchable_id')
|
||||
->whereColumn('favourites.watchable_type', '=', 'joint_permissions.entity_type');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getAvailableOptionNames(): array
|
||||
{
|
||||
return array_keys(static::$levelByOption);
|
||||
}
|
||||
|
||||
public static function optionNameToLevel(string $option): int
|
||||
{
|
||||
return static::$levelByOption[$option] ?? -1;
|
||||
}
|
||||
}
|
||||
|
|
98
app/Activity/Tools/UserWatchOptions.php
Normal file
98
app/Activity/Tools/UserWatchOptions.php
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
namespace BookStack\Activity\Tools;
|
||||
|
||||
use BookStack\Activity\Models\Watch;
|
||||
use BookStack\Entities\Models\Entity;
|
||||
use BookStack\Users\Models\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
|
||||
class UserWatchOptions
|
||||
{
|
||||
protected static array $levelByName = [
|
||||
'default' => -1,
|
||||
'ignore' => 0,
|
||||
'new' => 1,
|
||||
'updates' => 2,
|
||||
'comments' => 3,
|
||||
];
|
||||
|
||||
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 $this->levelValueToName($levelValue);
|
||||
}
|
||||
|
||||
public function isWatching(Entity $entity): bool
|
||||
{
|
||||
return $this->entityQuery($entity)->exists();
|
||||
}
|
||||
|
||||
public function updateEntityWatchLevel(Entity $entity, string $level): void
|
||||
{
|
||||
$levelValue = $this->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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public static function getAvailableLevelNames(): array
|
||||
{
|
||||
return array_keys(static::$levelByName);
|
||||
}
|
||||
|
||||
protected static function levelNameToValue(string $level): int
|
||||
{
|
||||
return static::$levelByName[$level] ?? -1;
|
||||
}
|
||||
|
||||
protected static function levelValueToName(int $level): string
|
||||
{
|
||||
foreach (static::$levelByName as $name => $value) {
|
||||
if ($level === $value) {
|
||||
return $name;
|
||||
}
|
||||
}
|
||||
|
||||
return 'default';
|
||||
}
|
||||
}
|
|
@ -5,6 +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\Entities\Models\Bookshelf;
|
||||
use BookStack\Entities\Repos\BookRepo;
|
||||
use BookStack\Entities\Tools\BookContents;
|
||||
|
@ -138,6 +139,7 @@ class BookController extends Controller
|
|||
'current' => $book,
|
||||
'bookChildren' => $bookChildren,
|
||||
'bookParentShelves' => $bookParentShelves,
|
||||
'watchOptions' => new UserWatchOptions(user()),
|
||||
'activity' => $activities->entityActivity($book, 20, 1),
|
||||
'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($book),
|
||||
]);
|
||||
|
|
|
@ -58,6 +58,9 @@ return [
|
|||
'favourite_add_notification' => '":name" has been added to your favourites',
|
||||
'favourite_remove_notification' => '":name" has been removed from your favourites',
|
||||
|
||||
// Watching
|
||||
'watch_update_level_notification' => 'Watch preferences successfully updated',
|
||||
|
||||
// Auth
|
||||
'auth_login' => 'logged in',
|
||||
'auth_register' => 'registered as new user',
|
||||
|
|
|
@ -417,4 +417,8 @@ return [
|
|||
'watch_title_comments' => 'All Page Updates & Comments',
|
||||
'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.',
|
||||
'watch_change_default' => 'Change default notification preferences',
|
||||
'watch_detail_ignore' => 'Ignoring notifications',
|
||||
'watch_detail_new' => 'Watching for new pages',
|
||||
'watch_detail_updates' => 'Watching new pages and updates',
|
||||
'watch_detail_comments' => 'Watching new pages, updates & comments',
|
||||
];
|
||||
|
|
1
resources/icons/watch-ignore.svg
Normal file
1
resources/icons/watch-ignore.svg
Normal file
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 7c2.76 0 5 2.24 5 5 0 .65-.13 1.26-.36 1.83l2.92 2.92c1.51-1.26 2.7-2.89 3.43-4.75-1.73-4.39-6-7.5-11-7.5-1.4 0-2.74.25-3.98.7l2.16 2.16C10.74 7.13 11.35 7 12 7zM2 4.27l2.28 2.28.46.46C3.08 8.3 1.78 10.02 1 12c1.73 4.39 6 7.5 11 7.5 1.55 0 3.03-.3 4.38-.84l.42.42L19.73 22 21 20.73 3.27 3 2 4.27zM7.53 9.8l1.55 1.55c-.05.21-.08.43-.08.65 0 1.66 1.34 3 3 3 .22 0 .44-.03.65-.08l1.55 1.55c-.67.33-1.41.53-2.2.53-2.76 0-5-2.24-5-5 0-.79.2-1.53.53-2.2zm4.31-.78l3.15 3.15.02-.16c0-1.66-1.34-3-3-3l-.17.01z"/></svg>
|
After Width: | Height: | Size: 583 B |
|
@ -1,4 +1,3 @@
|
|||
<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 0h24v24H0z" fill="none"/>
|
||||
<path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>
|
||||
</svg>
|
Before Width: | Height: | Size: 335 B After Width: | Height: | Size: 293 B |
|
@ -142,7 +142,9 @@
|
|||
@if(signedInUser())
|
||||
@include('entities.favourite-action', ['entity' => $book])
|
||||
@endif
|
||||
@include('entities.watch-action', ['entity' => $book])
|
||||
@if($watchOptions->canWatch() && !$watchOptions->isWatching($book))
|
||||
@include('entities.watch-action', ['entity' => $book])
|
||||
@endif
|
||||
@if(userCan('content-export'))
|
||||
@include('entities.export-menu', ['entity' => $book])
|
||||
@endif
|
||||
|
|
|
@ -69,12 +69,17 @@
|
|||
</a>
|
||||
@endif
|
||||
|
||||
<div component="dropdown"
|
||||
class="dropdown-container my-xxs">
|
||||
<a refs="dropdown@toggle" href="#" class="entity-meta-item my-none">
|
||||
@icon('watch')
|
||||
<span>Watching with default preferences</span>
|
||||
</a>
|
||||
@include('entities.watch-controls', ['entity' => $entity])
|
||||
</div>
|
||||
@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>
|
||||
@endif
|
||||
</div>
|
|
@ -1,8 +1,12 @@
|
|||
<form action="{{ $entity->getUrl('/') }}" method="GET">
|
||||
<form action="{{ url('/watching/update') }}" method="POST">
|
||||
{{ csrf_field() }}
|
||||
{{ method_field('PUT') }}
|
||||
<input type="hidden" name="type" value="{{ get_class($entity) }}">
|
||||
<input type="hidden" name="id" value="{{ $entity->id }}">
|
||||
<button type="submit" data-shortcut="favourite" class="icon-list-item text-link">
|
||||
<button type="submit"
|
||||
name="level"
|
||||
value="updates"
|
||||
class="icon-list-item text-link">
|
||||
<span>@icon('watch')</span>
|
||||
<span>{{ trans('entities.watch') }}</span>
|
||||
</button>
|
||||
|
|
|
@ -1,27 +1,30 @@
|
|||
<form action="{{ $entity->getUrl('/') }}" method="GET">
|
||||
{{-- {{ method_field('PUT') }}--}}
|
||||
<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\Models\Watch::getAvailableOptionNames() as $option)
|
||||
<li>
|
||||
<button name="level" value="{{ $option }}" class="icon-item">
|
||||
@if(request()->query('level') === $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) }}
|
||||
@foreach(\BookStack\Activity\Tools\UserWatchOptions::getAvailableLevelNames() as $option)
|
||||
<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>
|
||||
<li><hr class="my-none"></li>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<hr class="my-none">
|
||||
</li>
|
||||
@endforeach
|
||||
<li>
|
||||
<a href="{{ url('/preferences/notifications') }}"
|
||||
|
|
|
@ -194,6 +194,9 @@ Route::middleware('auth')->group(function () {
|
|||
Route::post('/favourites/add', [ActivityControllers\FavouriteController::class, 'add']);
|
||||
Route::post('/favourites/remove', [ActivityControllers\FavouriteController::class, 'remove']);
|
||||
|
||||
// Watching
|
||||
Route::put('/watching/update', [ActivityControllers\WatchController::class, 'update']);
|
||||
|
||||
// Other Pages
|
||||
Route::get('/', [HomeController::class, 'index']);
|
||||
Route::get('/home', [HomeController::class, 'index']);
|
||||
|
|
Loading…
Reference in a new issue