- 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.
namespace BookStack\Activity\Models;
+use BookStack\Activity\WatchLevels;
use BookStack\Permissions\Models\JointPermission;
use Carbon\Carbon;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
+use Illuminate\Database\Eloquent\Relations\MorphTo;
/**
* @property int $id
{
protected $guarded = [];
- public function watchable()
+ public function watchable(): MorphTo
{
- $this->morphTo();
+ return $this->morphTo();
}
public function jointPermissions(): HasMany
{
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;
}
}
namespace BookStack\Activity;
+use BookStack\Entities\Models\Bookshelf;
+use BookStack\Entities\Models\Entity;
+use BookStack\Entities\Models\Page;
+
class WatchLevels
{
/**
/**
* Get all the possible values as an option_name => value array.
+ * @returns array<string, int>
*/
public static function all(): array
{
return $options;
}
+ /**
+ * Get the watch options suited for the given entity.
+ * @returns array<string, int>
+ */
+ public static function allSuitedFor(Entity $entity): array
+ {
+ $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] ?? -1;
+ 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
{
foreach (static::all() as $name => $value) {
public function run(int $count, int $skip = 0)
{
$user = user();
- if (is_null($user) || $user->isDefault()) {
+ if ($user->isDefault()) {
return collect();
}
namespace BookStack\Users\Controllers;
+use BookStack\Activity\Models\Watch;
use BookStack\Http\Controller;
+use BookStack\Permissions\PermissionApplicator;
use BookStack\Settings\UserNotificationPreferences;
use BookStack\Settings\UserShortcutMap;
use BookStack\Users\UserRepo;
/**
* Show the notification preferences for the current user.
*/
- public function showNotifications()
+ public function showNotifications(PermissionApplicator $permissions)
{
$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', [
'preferences' => $preferences,
+ 'watches' => $watches,
]);
}
'watch_desc_new' => 'Notify when any new page is created within this item.',
'watch_title_updates' => 'All Page Updates',
'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_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_detail_ignore' => 'Ignoring notifications',
'watch_detail_new' => 'Watching for new pages',
'notifications_opt_comment_replies' => 'Notify upon replies to my comments',
'notifications_save' => 'Save Preferences',
'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.',
];
--- /dev/null
+<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>
\ No newline at end of file
<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)
+ @foreach(\BookStack\Activity\WatchLevels::allSuitedFor($entity) as $option => $value)
<li>
<button name="level" value="{{ $option }}" class="icon-item">
@if($watchLevel === $option)
<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) }}
+ @if(trans()->has('entities.watch_desc_' . $option . '_' . $entity->getMorphClass()))
+ {{ trans('entities.watch_desc_' . $option . '_' . $entity->getMorphClass()) }}
+ @else
+ {{ trans('entities.watch_desc_' . $option) }}
+ @endif
</div>
</div>
</button>
:</strong> {{ $activity->type }}</div>
<div class="flex-3 px-m py-xxs min-width-l">
@if($activity->entity)
- <a href="{{ $activity->entity->getUrl() }}" class="flex-container-row items-center">
- <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>
+ @include('entities.icon-link', ['entity' => $activity->entity])
@elseif($activity->detail && $activity->isForEntity())
<div>
{{ trans('settings.audit_deleted_item') }} <br>
</form>
</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>
@stop