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
|
@ -3,6 +3,7 @@
|
||||||
namespace BookStack\Activity\Controllers;
|
namespace BookStack\Activity\Controllers;
|
||||||
|
|
||||||
use BookStack\Activity\Models\Watch;
|
use BookStack\Activity\Models\Watch;
|
||||||
|
use BookStack\Activity\Tools\UserWatchOptions;
|
||||||
use BookStack\App\Model;
|
use BookStack\App\Model;
|
||||||
use BookStack\Entities\Models\Entity;
|
use BookStack\Entities\Models\Entity;
|
||||||
use BookStack\Http\Controller;
|
use BookStack\Http\Controller;
|
||||||
|
@ -19,13 +20,12 @@ class WatchController extends Controller
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$watchable = $this->getValidatedModelFromRequest($request);
|
$watchable = $this->getValidatedModelFromRequest($request);
|
||||||
$newLevel = Watch::optionNameToLevel($requestData['level']);
|
$watchOptions = new UserWatchOptions(user());
|
||||||
|
$watchOptions->updateEntityWatchLevel($watchable, $requestData['level']);
|
||||||
|
|
||||||
if ($newLevel < 0) {
|
$this->showSuccessNotification(trans('activities.watch_update_level_notification'));
|
||||||
// TODO - Delete
|
|
||||||
} else {
|
return redirect()->back();
|
||||||
// TODO - Upsert
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -18,13 +18,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
*/
|
*/
|
||||||
class Watch extends Model
|
class Watch extends Model
|
||||||
{
|
{
|
||||||
protected static array $levelByOption = [
|
protected $guarded = [];
|
||||||
'default' => -1,
|
|
||||||
'ignore' => 0,
|
|
||||||
'new' => 1,
|
|
||||||
'updates' => 2,
|
|
||||||
'comments' => 3,
|
|
||||||
];
|
|
||||||
|
|
||||||
public function watchable()
|
public function watchable()
|
||||||
{
|
{
|
||||||
|
@ -36,17 +30,4 @@ class Watch extends Model
|
||||||
return $this->hasMany(JointPermission::class, 'entity_id', 'watchable_id')
|
return $this->hasMany(JointPermission::class, 'entity_id', 'watchable_id')
|
||||||
->whereColumn('favourites.watchable_type', '=', 'joint_permissions.entity_type');
|
->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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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\ActivityQueries;
|
||||||
use BookStack\Activity\ActivityType;
|
use BookStack\Activity\ActivityType;
|
||||||
use BookStack\Activity\Models\View;
|
use BookStack\Activity\Models\View;
|
||||||
|
use BookStack\Activity\Tools\UserWatchOptions;
|
||||||
use BookStack\Entities\Models\Bookshelf;
|
use BookStack\Entities\Models\Bookshelf;
|
||||||
use BookStack\Entities\Repos\BookRepo;
|
use BookStack\Entities\Repos\BookRepo;
|
||||||
use BookStack\Entities\Tools\BookContents;
|
use BookStack\Entities\Tools\BookContents;
|
||||||
|
@ -138,6 +139,7 @@ class BookController extends Controller
|
||||||
'current' => $book,
|
'current' => $book,
|
||||||
'bookChildren' => $bookChildren,
|
'bookChildren' => $bookChildren,
|
||||||
'bookParentShelves' => $bookParentShelves,
|
'bookParentShelves' => $bookParentShelves,
|
||||||
|
'watchOptions' => new UserWatchOptions(user()),
|
||||||
'activity' => $activities->entityActivity($book, 20, 1),
|
'activity' => $activities->entityActivity($book, 20, 1),
|
||||||
'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($book),
|
'referenceCount' => $this->referenceFetcher->getPageReferenceCountToEntity($book),
|
||||||
]);
|
]);
|
||||||
|
|
|
@ -58,6 +58,9 @@ return [
|
||||||
'favourite_add_notification' => '":name" has been added to your favourites',
|
'favourite_add_notification' => '":name" has been added to your favourites',
|
||||||
'favourite_remove_notification' => '":name" has been removed from your favourites',
|
'favourite_remove_notification' => '":name" has been removed from your favourites',
|
||||||
|
|
||||||
|
// Watching
|
||||||
|
'watch_update_level_notification' => 'Watch preferences successfully updated',
|
||||||
|
|
||||||
// Auth
|
// Auth
|
||||||
'auth_login' => 'logged in',
|
'auth_login' => 'logged in',
|
||||||
'auth_register' => 'registered as new user',
|
'auth_register' => 'registered as new user',
|
||||||
|
|
|
@ -417,4 +417,8 @@ return [
|
||||||
'watch_title_comments' => 'All Page Updates & Comments',
|
'watch_title_comments' => 'All Page Updates & Comments',
|
||||||
'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.',
|
'watch_desc_comments' => 'Notify upon all new pages, page changes and new comments.',
|
||||||
'watch_change_default' => 'Change default notification preferences',
|
'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',
|
||||||
];
|
];
|
||||||
|
|
|
@ -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">
|
<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"/>
|
<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>
|
</svg>
|
Before Width: | Height: | Size: 335 B After Width: | Height: | Size: 293 B |
|
@ -142,7 +142,9 @@
|
||||||
@if(signedInUser())
|
@if(signedInUser())
|
||||||
@include('entities.favourite-action', ['entity' => $book])
|
@include('entities.favourite-action', ['entity' => $book])
|
||||||
@endif
|
@endif
|
||||||
@include('entities.watch-action', ['entity' => $book])
|
@if($watchOptions->canWatch() && !$watchOptions->isWatching($book))
|
||||||
|
@include('entities.watch-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
|
||||||
|
|
|
@ -69,12 +69,17 @@
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
<div component="dropdown"
|
@if($watchOptions?->canWatch() && $watchOptions->isWatching($entity))
|
||||||
class="dropdown-container my-xxs">
|
@php
|
||||||
<a refs="dropdown@toggle" href="#" class="entity-meta-item my-none">
|
$watchLevel = $watchOptions->getEntityWatchLevel($entity);
|
||||||
@icon('watch')
|
@endphp
|
||||||
<span>Watching with default preferences</span>
|
<div component="dropdown"
|
||||||
</a>
|
class="dropdown-container block my-xxs">
|
||||||
@include('entities.watch-controls', ['entity' => $entity])
|
<a refs="dropdown@toggle" href="#" class="entity-meta-item my-none">
|
||||||
</div>
|
@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>
|
</div>
|
|
@ -1,8 +1,12 @@
|
||||||
<form action="{{ $entity->getUrl('/') }}" method="GET">
|
<form action="{{ url('/watching/update') }}" method="POST">
|
||||||
{{ csrf_field() }}
|
{{ csrf_field() }}
|
||||||
|
{{ method_field('PUT') }}
|
||||||
<input type="hidden" name="type" value="{{ get_class($entity) }}">
|
<input type="hidden" name="type" value="{{ get_class($entity) }}">
|
||||||
<input type="hidden" name="id" value="{{ $entity->id }}">
|
<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>@icon('watch')</span>
|
||||||
<span>{{ trans('entities.watch') }}</span>
|
<span>{{ trans('entities.watch') }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
|
@ -1,27 +1,30 @@
|
||||||
<form action="{{ $entity->getUrl('/') }}" method="GET">
|
<form action="{{ url('/watching/update') }}" method="POST">
|
||||||
{{-- {{ method_field('PUT') }}--}}
|
{{ method_field('PUT') }}
|
||||||
{{ csrf_field() }}
|
{{ csrf_field() }}
|
||||||
<input type="hidden" name="type" value="{{ get_class($entity) }}">
|
<input type="hidden" name="type" value="{{ get_class($entity) }}">
|
||||||
<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\Models\Watch::getAvailableOptionNames() as $option)
|
@foreach(\BookStack\Activity\Tools\UserWatchOptions::getAvailableLevelNames() as $option)
|
||||||
<li>
|
<li>
|
||||||
<button name="level" value="{{ $option }}" class="icon-item">
|
<button name="level" value="{{ $option }}" class="icon-item">
|
||||||
@if(request()->query('level') === $option)
|
@if($watchLevel === $option)
|
||||||
<span class="text-pos pt-m" title="{{ trans('common.status_active') }}">@icon('check-circle')</span>
|
<span class="text-pos pt-m"
|
||||||
@else
|
title="{{ trans('common.status_active') }}">@icon('check-circle')</span>
|
||||||
<span title="{{ trans('common.status_inactive') }}"></span>
|
@else
|
||||||
@endif
|
<span title="{{ trans('common.status_inactive') }}"></span>
|
||||||
<div class="break-text">
|
@endif
|
||||||
<div class="mb-xxs"><strong>{{ trans('entities.watch_title_' . $option) }}</strong></div>
|
<div class="break-text">
|
||||||
<div class="text-muted text-small">
|
<div class="mb-xxs"><strong>{{ trans('entities.watch_title_' . $option) }}</strong></div>
|
||||||
{{ trans('entities.watch_desc_' . $option) }}
|
<div class="text-muted text-small">
|
||||||
|
{{ trans('entities.watch_desc_' . $option) }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
</button>
|
</li>
|
||||||
</li>
|
<li>
|
||||||
<li><hr class="my-none"></li>
|
<hr class="my-none">
|
||||||
|
</li>
|
||||||
@endforeach
|
@endforeach
|
||||||
<li>
|
<li>
|
||||||
<a href="{{ url('/preferences/notifications') }}"
|
<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/add', [ActivityControllers\FavouriteController::class, 'add']);
|
||||||
Route::post('/favourites/remove', [ActivityControllers\FavouriteController::class, 'remove']);
|
Route::post('/favourites/remove', [ActivityControllers\FavouriteController::class, 'remove']);
|
||||||
|
|
||||||
|
// Watching
|
||||||
|
Route::put('/watching/update', [ActivityControllers\WatchController::class, 'update']);
|
||||||
|
|
||||||
// Other Pages
|
// Other Pages
|
||||||
Route::get('/', [HomeController::class, 'index']);
|
Route::get('/', [HomeController::class, 'index']);
|
||||||
Route::get('/home', [HomeController::class, 'index']);
|
Route::get('/home', [HomeController::class, 'index']);
|
||||||
|
|
Loading…
Reference in New Issue