Started addition of favourite system

This commit is contained in:
Dan Brown 2021-05-16 00:29:56 +01:00
parent 7a059a5e90
commit bf8e7f3393
No known key found for this signature in database
GPG Key ID: 46D9F943C24A2EF9
13 changed files with 190 additions and 4 deletions

17
app/Actions/Favourite.php Normal file
View File

@ -0,0 +1,17 @@
<?php namespace BookStack\Actions;
use BookStack\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class Favourite extends Model
{
protected $fillable = ['user_id'];
/**
* Get the related model that can be favourited.
*/
public function favouritable(): MorphTo
{
return $this->morphTo();
}
}

View File

@ -1,6 +1,7 @@
<?php namespace BookStack\Actions;
use BookStack\Model;
use Illuminate\Database\Eloquent\Relations\MorphTo;
class View extends Model
{
@ -9,9 +10,8 @@ class View extends Model
/**
* Get all owning viewable models.
* @return \Illuminate\Database\Eloquent\Relations\MorphTo
*/
public function viewable()
public function viewable(): MorphTo
{
return $this->morphTo();
}

View File

@ -2,6 +2,7 @@
use BookStack\Actions\Activity;
use BookStack\Actions\Comment;
use BookStack\Actions\Favourite;
use BookStack\Actions\Tag;
use BookStack\Actions\View;
use BookStack\Auth\Permissions\EntityPermission;
@ -9,6 +10,7 @@ use BookStack\Auth\Permissions\JointPermission;
use BookStack\Entities\Tools\SearchIndex;
use BookStack\Entities\Tools\SlugGenerator;
use BookStack\Facades\Permissions;
use BookStack\Interfaces\Favouritable;
use BookStack\Interfaces\Sluggable;
use BookStack\Model;
use BookStack\Traits\HasCreatorAndUpdater;
@ -38,7 +40,7 @@ use Illuminate\Database\Eloquent\SoftDeletes;
* @method static Builder withLastView()
* @method static Builder withViewCount()
*/
abstract class Entity extends Model implements Sluggable
abstract class Entity extends Model implements Sluggable, Favouritable
{
use SoftDeletes;
use HasCreatorAndUpdater;
@ -297,4 +299,22 @@ abstract class Entity extends Model implements Sluggable
$this->slug = app(SlugGenerator::class)->generate($this);
return $this->slug;
}
/**
* @inheritdoc
*/
public function favourites(): MorphMany
{
return $this->morphMany(Favourite::class, 'favouritable');
}
/**
* Check if the entity is a favourite of the current user.
*/
public function isFavourite(): bool
{
return $this->favourites()
->where('user_id', '=', user()->id)
->exists();
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace BookStack\Http\Controllers;
use BookStack\Entities\Models\Entity;
use BookStack\Interfaces\Favouritable;
use BookStack\Model;
use Illuminate\Http\Request;
class FavouriteController extends Controller
{
/**
* Add a new item as a favourite.
*/
public function add(Request $request)
{
$favouritable = $this->getValidatedModelFromRequest($request);
$favouritable->favourites()->firstOrCreate([
'user_id' => user()->id,
]);
$this->showSuccessNotification(trans('activities.favourite_add_notification', [
'name' => $favouritable->name,
]));
return redirect()->back();
}
/**
* Remove an item as a favourite.
*/
public function remove(Request $request)
{
$favouritable = $this->getValidatedModelFromRequest($request);
$favouritable->favourites()->where([
'user_id' => user()->id,
])->delete();
$this->showSuccessNotification(trans('activities.favourite_remove_notification', [
'name' => $favouritable->name,
]));
return redirect()->back();
}
/**
* @throws \Illuminate\Validation\ValidationException
* @throws \Exception
*/
protected function getValidatedModelFromRequest(Request $request): Favouritable
{
$modelInfo = $this->validate($request, [
'type' => 'required|string',
'id' => 'required|integer',
]);
if (!class_exists($modelInfo['type'])) {
throw new \Exception('Model not found');
}
/** @var Model $model */
$model = new $modelInfo['type'];
if (! $model instanceof Favouritable) {
throw new \Exception('Model not favouritable');
}
$modelInstance = $model->newQuery()
->where('id', '=', $modelInfo['id'])
->first(['id', 'name']);
$inaccessibleEntity = ($modelInstance instanceof Entity && !userCan('view', $modelInstance));
if (is_null($modelInstance) || $inaccessibleEntity) {
throw new \Exception('Model instance not found');
}
return $modelInstance;
}
}

View File

@ -0,0 +1,11 @@
<?php namespace BookStack\Interfaces;
use Illuminate\Database\Eloquent\Relations\MorphMany;
interface Favouritable
{
/**
* Get the related favourite instances.
*/
public function favourites(): MorphMany;
}

View File

@ -0,0 +1,36 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
class CreateFavouritesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('favourites', function (Blueprint $table) {
$table->increments('id');
$table->integer('user_id')->index();
$table->integer('favouritable_id');
$table->string('favouritable_type', 100);
$table->timestamps();
$table->index(['favouritable_id', 'favouritable_type'], 'favouritable_index');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::dropIfExists('favourites');
}
}

View File

@ -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="M11.99 2C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zm4.24 16L12 15.45 7.77 18l1.12-4.81-3.73-3.23 4.92-.42L12 5l1.92 4.53 4.92.42-3.73 3.23L16.23 18z"/>
</svg>

Before

Width:  |  Height:  |  Size: 307 B

After

Width:  |  Height:  |  Size: 265 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M22 9.24l-7.19-.62L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21 12 17.27 18.18 21l-1.63-7.03L22 9.24zM12 15.4l-3.76 2.27 1-4.28-3.32-2.88 4.38-.38L12 6.1l1.71 4.04 4.38.38-3.32 2.88 1 4.28L12 15.4z"/></svg>

After

Width:  |  Height:  |  Size: 270 B

View File

@ -43,6 +43,10 @@ return [
'bookshelf_delete' => 'deleted bookshelf',
'bookshelf_delete_notification' => 'Bookshelf Successfully Deleted',
// Favourites
'favourite_add_notification' => '":name" has been added to your favourites',
'favourite_remove_notification' => '":name" has been removed from your favourites',
// Other
'commented_on' => 'commented on',
'permissions_update' => 'updated permissions',

View File

@ -40,6 +40,8 @@ return [
'remove' => 'Remove',
'add' => 'Add',
'fullscreen' => 'Fullscreen',
'favourite' => 'Favourite',
'unfavourite' => 'Unfavourite',
// Sort Options
'sort_options' => 'Sort Options',

View File

@ -151,6 +151,9 @@
<hr class="primary-background"/>
{{--Export--}}
@if(signedInUser())
@include('partials.entity-favourite-action', ['entity' => $page, 'alreadyFavourite' => $page->isFavourite()])
@endif
@include('partials.entity-export-menu', ['entity' => $page])
</div>

View File

@ -0,0 +1,12 @@
@php
$isFavourite = $entity->isFavourite();
@endphp
<form action="{{ url('/favourites/' . ($isFavourite ? 'remove' : 'add')) }}" method="POST">
{{ csrf_field() }}
<input type="hidden" name="type" value="{{ get_class($entity) }}">
<input type="hidden" name="id" value="{{ $entity->id }}">
<button type="submit" class="icon-list-item text-primary">
<span>@icon($isFavourite ? 'star' : 'star-outline')</span>
<span>{{ $isFavourite ? trans('common.unfavourite') : trans('common.favourite') }}</span>
</button>
</form>

View File

@ -152,9 +152,14 @@ Route::group(['middleware' => 'auth'], function () {
// User Search
Route::get('/search/users/select', 'UserSearchController@forSelect');
// Template System
Route::get('/templates', 'PageTemplateController@list');
Route::get('/templates/{templateId}', 'PageTemplateController@get');
// Favourites
Route::post('/favourites/add', 'FavouriteController@add');
Route::post('/favourites/remove', 'FavouriteController@remove');
// Other Pages
Route::get('/', 'HomeController@index');
Route::get('/home', 'HomeController@index');