mirror of https://github.com/MarceauKa/shaark.git
✨ Pinned posts
This commit is contained in:
parent
3fc6ad5706
commit
a394fb01fe
|
@ -27,6 +27,7 @@ class ChestController extends Controller
|
|||
])->toArray());
|
||||
|
||||
$post = new Post(['is_private' => true, 'user_id' => $request->user()->id]);
|
||||
$post->is_pinned = $data;
|
||||
$post->postable()->associate($chest)->save();
|
||||
|
||||
if ($data['tags']) {
|
||||
|
@ -47,6 +48,7 @@ class ChestController extends Controller
|
|||
$data = collect($request->validated());
|
||||
|
||||
$chest->fill($data->only('title', 'content')->toArray());
|
||||
$chest->post->is_pinned = $data->get('is_pinned', $chest->post->is_pinned);
|
||||
|
||||
if ($data['tags']) {
|
||||
$chest->post->syncTags($data['tags']);
|
||||
|
|
|
@ -58,7 +58,8 @@ class LinkController extends Controller
|
|||
$link->updatePreview();
|
||||
|
||||
$post = new Post();
|
||||
$post->is_private = $data->get('is_private', 0);
|
||||
$post->is_pinned = $data;
|
||||
$post->is_private = $data;
|
||||
$post->user_id = $request->user()->id;
|
||||
$post->postable()->associate($link)->save();
|
||||
|
||||
|
@ -83,6 +84,7 @@ class LinkController extends Controller
|
|||
$link->fill($data->only('title', 'content', 'url')->toArray());
|
||||
$link->updatePreview();
|
||||
|
||||
$link->post->is_pinned = $data->get('is_pinned', $link->post->is_pinned);
|
||||
$link->post->is_private = $data->get('is_private', $link->post->is_private);
|
||||
$link->post->save();
|
||||
|
||||
|
|
|
@ -28,7 +28,8 @@ class StoryController extends Controller
|
|||
])->toArray());
|
||||
|
||||
$post = new Post();
|
||||
$post->is_private = $data->get('is_private', 0);
|
||||
$post->is_pinned = $data;
|
||||
$post->is_private = $data;
|
||||
$post->user_id = $request->user()->id;
|
||||
$post->postable()->associate($story)->save();
|
||||
|
||||
|
@ -50,6 +51,7 @@ class StoryController extends Controller
|
|||
$data = collect($request->validated());
|
||||
|
||||
$story->fill($data->only('title', 'slug', 'content')->toArray());
|
||||
$story->post->is_pinned = $data->get('is_pinned', $story->post->is_pinned);
|
||||
$story->post->is_private = $data->get('is_private', $story->post->is_private);
|
||||
|
||||
if ($data['tags']) {
|
||||
|
|
|
@ -22,6 +22,7 @@ class BrowseController extends Controller
|
|||
}
|
||||
|
||||
$posts = $posts->withPrivate($request)
|
||||
->pinnedFirst()
|
||||
->latest()
|
||||
->paginate(20);
|
||||
|
||||
|
@ -87,6 +88,7 @@ class BrowseController extends Controller
|
|||
$tag = Tag::named($tag)->firstOrFail();
|
||||
|
||||
$posts = Post::withPrivate($request)
|
||||
->pinnedFirst()
|
||||
->with('postable', 'tags')
|
||||
->withAllTags($tag)
|
||||
->paginate(20);
|
||||
|
|
|
@ -22,6 +22,9 @@ class StoreChestRequest extends FormRequest
|
|||
'content' => [
|
||||
'array',
|
||||
],
|
||||
'is_pinned' => [
|
||||
'nullable',
|
||||
],
|
||||
'tags' => [
|
||||
'nullable',
|
||||
'array',
|
||||
|
|
|
@ -30,6 +30,9 @@ class StoreLinkRequest extends FormRequest
|
|||
'is_private' => [
|
||||
'nullable',
|
||||
],
|
||||
'is_pinned' => [
|
||||
'nullable',
|
||||
],
|
||||
'tags' => [
|
||||
'nullable',
|
||||
'array',
|
||||
|
|
|
@ -37,6 +37,9 @@ class StoreStoryRequest extends FormRequest
|
|||
'is_private' => [
|
||||
'nullable',
|
||||
],
|
||||
'is_pinned' => [
|
||||
'nullable',
|
||||
],
|
||||
'tags' => [
|
||||
'nullable',
|
||||
'array',
|
||||
|
|
|
@ -15,6 +15,7 @@ class ChestResource extends JsonResource
|
|||
'url' => $this->permalink,
|
||||
'permalink' => $this->permalink,
|
||||
'is_private' => true,
|
||||
'is_pinned' => $this->post->is_pinned,
|
||||
'date_formated' => $this->created_at->diffForHumans(),
|
||||
'tags' => $this->post->tags->pluck('name')->toArray(),
|
||||
$this->mergeWhen(Auth::check(), [
|
||||
|
|
|
@ -15,6 +15,7 @@ class LinkResource extends JsonResource
|
|||
'url' => $this->url,
|
||||
'permalink' => $this->permalink,
|
||||
'is_private' => $this->post->is_private,
|
||||
'is_pinned' => $this->post->is_pinned,
|
||||
'preview' => $this->preview,
|
||||
'date_formated' => $this->created_at->diffForHumans(),
|
||||
'tags' => $this->post->tags->pluck('name')->toArray(),
|
||||
|
|
|
@ -39,6 +39,7 @@ class PostResource extends JsonResource
|
|||
'postable_id' => $this->postable->id,
|
||||
'tags' => $this->tags->pluck('name')->toArray(),
|
||||
'is_private' => $this->is_private,
|
||||
'is_pinned' => $this->is_pinned,
|
||||
'created_at' => $this->created_at,
|
||||
]);
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@ class StoryResource extends JsonResource
|
|||
'content' => $this->content,
|
||||
'url' => $this->url,
|
||||
'is_private' => $this->post->is_private,
|
||||
'is_pinned' => $this->post->is_pinned,
|
||||
'date_formated' => $this->created_at->diffForHumans(),
|
||||
'tags' => $this->post->tags->pluck('name')->toArray(),
|
||||
$this->mergeWhen(Auth::check(), [
|
||||
|
|
27
app/Post.php
27
app/Post.php
|
@ -9,9 +9,11 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Collection;
|
||||
use Laravel\Scout\Searchable;
|
||||
|
||||
/**
|
||||
* @method Builder|Post pinnedFirst()
|
||||
* @method Builder|Post withPrivate(bool|User|Request $private)
|
||||
* @method Builder|Post withoutChests()
|
||||
*/
|
||||
|
@ -24,10 +26,12 @@ class Post extends Model
|
|||
'postable_type',
|
||||
'postable_id',
|
||||
'is_private',
|
||||
'is_pinned',
|
||||
'user_id',
|
||||
'created_at',
|
||||
];
|
||||
protected $casts = [
|
||||
'is_pinned' => 'bool',
|
||||
'is_private' => 'bool',
|
||||
];
|
||||
|
||||
|
@ -51,6 +55,24 @@ class Post extends Model
|
|||
return $this->created_at->diffForHumans();
|
||||
}
|
||||
|
||||
public function setIsPinnedAttribute($value): void
|
||||
{
|
||||
if ($value instanceof Collection) {
|
||||
$value = (bool)$value->get('is_pinned', false);
|
||||
}
|
||||
|
||||
$this->attributes['is_pinned'] = $value;
|
||||
}
|
||||
|
||||
public function setIsPrivateAttribute($value): void
|
||||
{
|
||||
if ($value instanceof Collection) {
|
||||
$value = (bool)$value->get('is_private', false);
|
||||
}
|
||||
|
||||
$this->attributes['is_private'] = $value;
|
||||
}
|
||||
|
||||
public function scopeWithPrivate(Builder $query, $user = null): Builder
|
||||
{
|
||||
if ($user instanceof Request) {
|
||||
|
@ -74,6 +96,11 @@ class Post extends Model
|
|||
return $query;
|
||||
}
|
||||
|
||||
public function scopePinnedFirst(Builder $query): Builder
|
||||
{
|
||||
return $query->orderByDesc('is_pinned');
|
||||
}
|
||||
|
||||
public function scopeWithoutChests(Builder $query): Builder
|
||||
{
|
||||
return $query->where('postable_type', '!=', Chest::class);
|
||||
|
|
|
@ -1,3 +1,11 @@
|
|||
# Unreleased
|
||||
|
||||
⚠️ Run migrations when updating
|
||||
|
||||
## Added
|
||||
|
||||
- Pinned posts
|
||||
|
||||
# 1.2.20
|
||||
|
||||
⚠️ Run migrations when updating
|
||||
|
|
|
@ -10,6 +10,7 @@ use Faker\Generator as Faker;
|
|||
$factory->define(Post::class, function (Faker $faker) {
|
||||
return [
|
||||
'is_private' => 0,
|
||||
'is_pinned' => 0,
|
||||
'user_id' => 1,
|
||||
];
|
||||
});
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
|
||||
class AddIsPinnedToPostsTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
Schema::table('posts', function (Blueprint $table) {
|
||||
$table->boolean('is_pinned')->index()->default(false)->after('is_private');
|
||||
});
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
Schema::table('posts', function (Blueprint $table) {
|
||||
$table->dropColumn('is_pinned');
|
||||
});
|
||||
}
|
||||
}
|
File diff suppressed because one or more lines are too long
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"/js/app.js": "/js/app.js?id=e7b948d4deb0a01e7e9e",
|
||||
"/js/app.js": "/js/app.js?id=a9868a4cd1f7c234776c",
|
||||
"/css/app.css": "/css/app.css?id=076c4b4fab9ab74f3edc",
|
||||
"/js/manifest.js": "/js/manifest.js?id=3c768977c2574a34506e",
|
||||
"/js/vendor.js": "/js/vendor.js?id=24c264fe7e2a39bcb572"
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="card card--chest mb-4" :class="{'card-single': single, 'card-index': !single}">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
<i class="fas fa-thumbtack fa-sm pr-1" v-if="chest.is_pinned && !single"></i>
|
||||
<span>{{ __('Chest') }}</span> — <a :href="chest.permalink">{{ chest.title }}</a>
|
||||
</h5>
|
||||
|
||||
|
|
|
@ -12,6 +12,13 @@
|
|||
<chest-lines v-model="form.content"></chest-lines>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="is_pinned" v-model="form.is_pinned" :disabled="loading">
|
||||
<label class="custom-control-label" for="is_pinned" dusk="chest-form-pinned">{{ __('Is pinned?') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>{{ __('Tags') }}</label>
|
||||
<tags v-model="form.tags"></tags>
|
||||
|
@ -33,6 +40,7 @@ let defaultChest = function () {
|
|||
return {
|
||||
title: '',
|
||||
content: [],
|
||||
is_pinned: false,
|
||||
tags: [],
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="card card--link mb-4" :class="{'card-single': single, 'card-index': !single}">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
<i class="fas fa-thumbtack fa-sm pr-1" v-if="link.is_pinned && !single"></i>
|
||||
<span>{{ __('Link') }}</span> — <a :href="link.permalink">{{ link.title }}</a><br>
|
||||
<a :href="link.url" class="small text-muted">{{ displayUrl }}</a>
|
||||
</h5>
|
||||
|
|
|
@ -20,10 +20,23 @@
|
|||
<span class="invalid-feedback" v-if="hasFormError('content')">{{ firstFormError('content') }}</span>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="is_private" v-model="form.is_private" :disabled="loading">
|
||||
<label class="custom-control-label" for="is_private" dusk="link-form-private">{{ __('Private link?') }}</label>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="is_private" v-model="form.is_private" :disabled="loading">
|
||||
<label class="custom-control-label" for="is_private" dusk="link-form-private">{{ __('Private link?') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="is_pinned" v-model="form.is_pinned" :disabled="loading">
|
||||
<label class="custom-control-label" for="is_pinned" dusk="link-form-pinned">{{ __('Is pinned?') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -58,6 +71,7 @@ let defaultLink = function () {
|
|||
title: null,
|
||||
content: null,
|
||||
is_private: false,
|
||||
is_pinned: false,
|
||||
tags: []
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
<div class="card card--story mb-4" :class="{'card-single': single, 'card-index': !single}">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">
|
||||
<i class="fas fa-thumbtack fa-sm pr-1" v-if="story.is_pinned && !single"></i>
|
||||
<span>{{ __('Story') }}</span> — <a :href="story.url">{{ story.title }}</a>
|
||||
</h5>
|
||||
|
||||
|
|
|
@ -17,10 +17,23 @@
|
|||
></editor>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="is_private" v-model="form.is_private" :disabled="loading">
|
||||
<label class="custom-control-label" for="is_private">{{ __('Private story?') }}</label>
|
||||
<div class="row">
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="is_private" v-model="form.is_private" :disabled="loading">
|
||||
<label class="custom-control-label" for="is_private" dusk="link-form-private">{{ __('Private link?') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-switch">
|
||||
<input type="checkbox" class="custom-control-input" id="is_pinned" v-model="form.is_pinned" :disabled="loading">
|
||||
<label class="custom-control-label" for="is_pinned" dusk="link-form-pinned">{{ __('Is pinned?') }}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -47,6 +60,7 @@ let defaultStory = function () {
|
|||
slug: '',
|
||||
content: '',
|
||||
is_private: false,
|
||||
is_pinned: false,
|
||||
tags: [],
|
||||
};
|
||||
};
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
"Reset": "Zurücksetzen",
|
||||
"Generate": "Generate",
|
||||
"Loading": "Laden",
|
||||
"Is pinned?": "Is pinned?",
|
||||
|
||||
"Type / to search": "Tippe zur Suche",
|
||||
|
||||
|
|
|
@ -90,6 +90,7 @@
|
|||
"Reset": "Réinitialiser",
|
||||
"Generate": "Générer",
|
||||
"Loading": "Chargement",
|
||||
"Is pinned?": "Épinglé ?",
|
||||
|
||||
"Type / to search": "Tapez / pour chercher",
|
||||
|
||||
|
|
Loading…
Reference in New Issue