mirror of
https://github.com/BookStackApp/BookStack.git
synced 2024-10-01 01:36:00 -04:00
Notifications: User watch list and differnt page watch options
- Adds option filtering and alternative text for page watch options. - Adds "Watched & Ignored Items" list to user notification preferences page to show existing watched items.
This commit is contained in:
parent
c47b3f805a
commit
d9fdecd902
@ -2,10 +2,12 @@
|
|||||||
|
|
||||||
namespace BookStack\Activity\Models;
|
namespace BookStack\Activity\Models;
|
||||||
|
|
||||||
|
use BookStack\Activity\WatchLevels;
|
||||||
use BookStack\Permissions\Models\JointPermission;
|
use BookStack\Permissions\Models\JointPermission;
|
||||||
use Carbon\Carbon;
|
use Carbon\Carbon;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
|
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @property int $id
|
* @property int $id
|
||||||
@ -20,14 +22,24 @@ class Watch extends Model
|
|||||||
{
|
{
|
||||||
protected $guarded = [];
|
protected $guarded = [];
|
||||||
|
|
||||||
public function watchable()
|
public function watchable(): MorphTo
|
||||||
{
|
{
|
||||||
$this->morphTo();
|
return $this->morphTo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function jointPermissions(): HasMany
|
public function jointPermissions(): HasMany
|
||||||
{
|
{
|
||||||
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('watches.watchable_type', '=', 'joint_permissions.entity_type');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLevelName(): string
|
||||||
|
{
|
||||||
|
return WatchLevels::levelValueToName($this->level);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ignoring(): bool
|
||||||
|
{
|
||||||
|
return $this->level === WatchLevels::IGNORE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
namespace BookStack\Activity;
|
namespace BookStack\Activity;
|
||||||
|
|
||||||
|
use BookStack\Entities\Models\Bookshelf;
|
||||||
|
use BookStack\Entities\Models\Entity;
|
||||||
|
use BookStack\Entities\Models\Page;
|
||||||
|
|
||||||
class WatchLevels
|
class WatchLevels
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@ -32,6 +36,7 @@ class WatchLevels
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all the possible values as an option_name => value array.
|
* Get all the possible values as an option_name => value array.
|
||||||
|
* @returns array<string, int>
|
||||||
*/
|
*/
|
||||||
public static function all(): array
|
public static function all(): array
|
||||||
{
|
{
|
||||||
@ -43,11 +48,36 @@ class WatchLevels
|
|||||||
return $options;
|
return $options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function levelNameToValue(string $level): int
|
/**
|
||||||
|
* Get the watch options suited for the given entity.
|
||||||
|
* @returns array<string, int>
|
||||||
|
*/
|
||||||
|
public static function allSuitedFor(Entity $entity): array
|
||||||
{
|
{
|
||||||
return static::all()[$level] ?? -1;
|
$options = static::all();
|
||||||
|
|
||||||
|
if ($entity instanceof Page) {
|
||||||
|
unset($options['new']);
|
||||||
|
} elseif ($entity instanceof Bookshelf) {
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the given name to a level value.
|
||||||
|
* Defaults to default value if the level does not exist.
|
||||||
|
*/
|
||||||
|
public static function levelNameToValue(string $level): int
|
||||||
|
{
|
||||||
|
return static::all()[$level] ?? static::DEFAULT;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert the given int level value to a level name.
|
||||||
|
* Defaults to 'default' level name if not existing.
|
||||||
|
*/
|
||||||
public static function levelValueToName(int $level): string
|
public static function levelValueToName(int $level): string
|
||||||
{
|
{
|
||||||
foreach (static::all() as $name => $value) {
|
foreach (static::all() as $name => $value) {
|
||||||
|
@ -10,7 +10,7 @@ class TopFavourites extends EntityQuery
|
|||||||
public function run(int $count, int $skip = 0)
|
public function run(int $count, int $skip = 0)
|
||||||
{
|
{
|
||||||
$user = user();
|
$user = user();
|
||||||
if (is_null($user) || $user->isDefault()) {
|
if ($user->isDefault()) {
|
||||||
return collect();
|
return collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,9 @@
|
|||||||
|
|
||||||
namespace BookStack\Users\Controllers;
|
namespace BookStack\Users\Controllers;
|
||||||
|
|
||||||
|
use BookStack\Activity\Models\Watch;
|
||||||
use BookStack\Http\Controller;
|
use BookStack\Http\Controller;
|
||||||
|
use BookStack\Permissions\PermissionApplicator;
|
||||||
use BookStack\Settings\UserNotificationPreferences;
|
use BookStack\Settings\UserNotificationPreferences;
|
||||||
use BookStack\Settings\UserShortcutMap;
|
use BookStack\Settings\UserShortcutMap;
|
||||||
use BookStack\Users\UserRepo;
|
use BookStack\Users\UserRepo;
|
||||||
@ -49,12 +51,17 @@ class UserPreferencesController extends Controller
|
|||||||
/**
|
/**
|
||||||
* Show the notification preferences for the current user.
|
* Show the notification preferences for the current user.
|
||||||
*/
|
*/
|
||||||
public function showNotifications()
|
public function showNotifications(PermissionApplicator $permissions)
|
||||||
{
|
{
|
||||||
$preferences = (new UserNotificationPreferences(user()));
|
$preferences = (new UserNotificationPreferences(user()));
|
||||||
|
|
||||||
|
$query = Watch::query()->where('user_id', '=', user()->id);
|
||||||
|
$query = $permissions->restrictEntityRelationQuery($query, 'watches', 'watchable_id', 'watchable_type');
|
||||||
|
$watches = $query->with('watchable')->paginate(20);
|
||||||
|
|
||||||
return view('users.preferences.notifications', [
|
return view('users.preferences.notifications', [
|
||||||
'preferences' => $preferences,
|
'preferences' => $preferences,
|
||||||
|
'watches' => $watches,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -414,8 +414,10 @@ return [
|
|||||||
'watch_desc_new' => 'Notify when any new page is created within this item.',
|
'watch_desc_new' => 'Notify when any new page is created within this item.',
|
||||||
'watch_title_updates' => 'All Page Updates',
|
'watch_title_updates' => 'All Page Updates',
|
||||||
'watch_desc_updates' => 'Notify upon all new pages and page changes.',
|
'watch_desc_updates' => 'Notify upon all new pages and page changes.',
|
||||||
|
'watch_desc_updates_page' => 'Notify upon all page changes.',
|
||||||
'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_desc_comments_page' => 'Notify upon 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_ignore' => 'Ignoring notifications',
|
||||||
'watch_detail_new' => 'Watching for new pages',
|
'watch_detail_new' => 'Watching for new pages',
|
||||||
|
@ -23,4 +23,6 @@ return [
|
|||||||
'notifications_opt_comment_replies' => 'Notify upon replies to my comments',
|
'notifications_opt_comment_replies' => 'Notify upon replies to my comments',
|
||||||
'notifications_save' => 'Save Preferences',
|
'notifications_save' => 'Save Preferences',
|
||||||
'notifications_update_success' => 'Notification preferences have been updated!',
|
'notifications_update_success' => 'Notification preferences have been updated!',
|
||||||
|
'notifications_watched' => 'Watched & Ignored Items',
|
||||||
|
'notifications_watched_desc' => ' Below are the items that have custom watch preferences applied. To update your preferences for these, view the item then find the watch options in the sidebar.',
|
||||||
];
|
];
|
||||||
|
7
resources/views/entities/icon-link.blade.php
Normal file
7
resources/views/entities/icon-link.blade.php
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<a href="{{ $entity->getUrl() }}" class="flex-container-row items-center">
|
||||||
|
<span role="presentation"
|
||||||
|
class="icon flex-none text-{{$entity->getType()}}">@icon($entity->getType())</span>
|
||||||
|
<div class="flex text-{{ $entity->getType() }}">
|
||||||
|
{{ $entity->name }}
|
||||||
|
</div>
|
||||||
|
</a>
|
@ -11,7 +11,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 => $value)
|
@foreach(\BookStack\Activity\WatchLevels::allSuitedFor($entity) 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)
|
||||||
@ -23,7 +23,11 @@
|
|||||||
<div class="break-text">
|
<div class="break-text">
|
||||||
<div class="mb-xxs"><strong>{{ trans('entities.watch_title_' . $option) }}</strong></div>
|
<div class="mb-xxs"><strong>{{ trans('entities.watch_title_' . $option) }}</strong></div>
|
||||||
<div class="text-muted text-small">
|
<div class="text-muted text-small">
|
||||||
|
@if(trans()->has('entities.watch_desc_' . $option . '_' . $entity->getMorphClass()))
|
||||||
|
{{ trans('entities.watch_desc_' . $option . '_' . $entity->getMorphClass()) }}
|
||||||
|
@else
|
||||||
{{ trans('entities.watch_desc_' . $option) }}
|
{{ trans('entities.watch_desc_' . $option) }}
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
@ -95,13 +95,7 @@
|
|||||||
:</strong> {{ $activity->type }}</div>
|
:</strong> {{ $activity->type }}</div>
|
||||||
<div class="flex-3 px-m py-xxs min-width-l">
|
<div class="flex-3 px-m py-xxs min-width-l">
|
||||||
@if($activity->entity)
|
@if($activity->entity)
|
||||||
<a href="{{ $activity->entity->getUrl() }}" class="flex-container-row items-center">
|
@include('entities.icon-link', ['entity' => $activity->entity])
|
||||||
<span role="presentation"
|
|
||||||
class="icon flex-none text-{{$activity->entity->getType()}}">@icon($activity->entity->getType())</span>
|
|
||||||
<div class="flex text-{{ $activity->entity->getType() }}">
|
|
||||||
{{ $activity->entity->name }}
|
|
||||||
</div>
|
|
||||||
</a>
|
|
||||||
@elseif($activity->detail && $activity->isForEntity())
|
@elseif($activity->detail && $activity->isForEntity())
|
||||||
<div>
|
<div>
|
||||||
{{ trans('settings.audit_deleted_item') }} <br>
|
{{ trans('settings.audit_deleted_item') }} <br>
|
||||||
|
@ -41,5 +41,30 @@
|
|||||||
</form>
|
</form>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<section class="card content-wrap auto-height">
|
||||||
|
<h2 class="list-heading">{{ trans('preferences.notifications_watched') }}</h2>
|
||||||
|
<p class="text-small text-muted">{{ trans('preferences.notifications_watched_desc') }}</p>
|
||||||
|
|
||||||
|
@if($watches->isEmpty())
|
||||||
|
<p class="text-muted italic">{{ trans('common.no_items') }}</p>
|
||||||
|
@else
|
||||||
|
<div class="item-list">
|
||||||
|
@foreach($watches as $watch)
|
||||||
|
<div class="flex-container-row justify-space-between item-list-row items-center wrap px-m py-s">
|
||||||
|
<div class="py-xs px-s min-width-m">
|
||||||
|
@include('entities.icon-link', ['entity' => $watch->watchable])
|
||||||
|
</div>
|
||||||
|
<div class="py-xs min-width-m text-m-right px-m">
|
||||||
|
@icon('watch' . ($watch->ignoring() ? '-ignore' : ''))
|
||||||
|
{{ trans('entities.watch_title_' . $watch->getLevelName()) }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<div class="my-m">{{ $watches->links() }}</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@stop
|
@stop
|
||||||
|
Loading…
Reference in New Issue
Block a user