mirror of
https://github.com/HDInnovations/UNIT3D-Community-Edition.git
synced 2026-04-26 13:09:10 -05:00
add: store unread news notifications
This commit is contained in:
@@ -17,6 +17,7 @@ declare(strict_types=1);
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\Article;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
/**
|
||||
* @see \Tests\Feature\Http\Controllers\ArticleControllerTest
|
||||
@@ -36,8 +37,10 @@ class ArticleController extends Controller
|
||||
/**
|
||||
* Show A Article.
|
||||
*/
|
||||
public function show(Article $article): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
public function show(Request $request, Article $article): \Illuminate\Contracts\View\Factory|\Illuminate\View\View
|
||||
{
|
||||
$article->unreads()->whereBelongsTo($request->user())->delete();
|
||||
|
||||
return view('article.show', [
|
||||
'article' => $article->load(['user', 'comments']),
|
||||
]);
|
||||
|
||||
@@ -46,13 +46,6 @@ class HomeController extends Controller
|
||||
// Authorized User
|
||||
$user = $request->user();
|
||||
|
||||
// Latest Articles/News Block
|
||||
$articles = cache()->remember('latest_article', $expiresAt, fn () => Article::latest()->take(1)->get());
|
||||
|
||||
foreach ($articles as $article) {
|
||||
$article->newNews = ($user->last_login->subDays(3)->getTimestamp() < $article->created_at->getTimestamp()) ? 1 : 0;
|
||||
}
|
||||
|
||||
return view('home.index', [
|
||||
'user' => $user,
|
||||
'users' => cache()->remember(
|
||||
@@ -82,8 +75,12 @@ class HomeController extends Controller
|
||||
->oldest('position')
|
||||
->get()
|
||||
),
|
||||
'articles' => $articles,
|
||||
'topics' => Topic::query()
|
||||
'articles' => Article::query()
|
||||
->latest()
|
||||
->limit(3)
|
||||
->withExists(['unreads' => fn ($query) => $query->whereBelongsTo($user)])
|
||||
->get(),
|
||||
'topics' => Topic::query()
|
||||
->with(['user', 'user.group', 'latestPoster', 'reads' => fn ($query) => $query->whereBelongsTo($user)])
|
||||
->authorized(canReadTopic: true)
|
||||
->latest()
|
||||
|
||||
@@ -20,6 +20,8 @@ use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Staff\StoreArticleRequest;
|
||||
use App\Http\Requests\Staff\UpdateArticleRequest;
|
||||
use App\Models\Article;
|
||||
use App\Models\UnreadArticle;
|
||||
use App\Models\User;
|
||||
use Intervention\Image\Facades\Image;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@@ -65,7 +67,15 @@ class ArticleController extends Controller
|
||||
Image::make($image->getRealPath())->fit(75, 75)->encode('png', 100)->save($path);
|
||||
}
|
||||
|
||||
Article::create(['user_id' => $request->user()->id, 'image' => $filename ?? null] + $request->validated());
|
||||
$article = Article::create(['user_id' => $request->user()->id, 'image' => $filename ?? null] + $request->validated());
|
||||
|
||||
UnreadArticle::query()->insertUsing(
|
||||
['article_id', 'user_id'],
|
||||
User::query()
|
||||
->selectRaw('?', [$article->id])
|
||||
->addSelect('id')
|
||||
->whereHas('group', fn ($query) => $query->whereNotIn('slug', ['validating', 'pruned', 'banned', 'disabled']))
|
||||
);
|
||||
|
||||
return to_route('staff.articles.index')
|
||||
->with('success', 'Your article has successfully published!');
|
||||
|
||||
@@ -65,4 +65,12 @@ class Article extends Model
|
||||
{
|
||||
return $this->morphMany(Comment::class, 'commentable');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Illuminate\Database\Eloquent\Relations\HasMany<UnreadArticle, $this>
|
||||
*/
|
||||
public function unreads(): \Illuminate\Database\Eloquent\Relations\HasMany
|
||||
{
|
||||
return $this->HasMany(UnreadArticle::class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* NOTICE OF LICENSE.
|
||||
*
|
||||
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
|
||||
* The details is bundled with this project in the file LICENSE.txt.
|
||||
*
|
||||
* @project UNIT3D Community Edition
|
||||
*
|
||||
* @author Roardom <roardom@protonmail.com>
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
|
||||
*/
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
/**
|
||||
* App\Models\UnreadArticle.
|
||||
*
|
||||
* @property int $id
|
||||
* @property int $article_id
|
||||
* @property int $user_id
|
||||
*/
|
||||
class UnreadArticle extends Model
|
||||
{
|
||||
/**
|
||||
* Indicates if the model should be timestamped.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
public $timestamps = false;
|
||||
|
||||
/**
|
||||
* The attributes that aren't mass assignable.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $guarded = [];
|
||||
|
||||
/**
|
||||
* Belongs to an article.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<Torrent, $this>
|
||||
*/
|
||||
public function article(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Article::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Belongs to a user.
|
||||
*
|
||||
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo<User, $this>
|
||||
*/
|
||||
public function user(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class);
|
||||
}
|
||||
}
|
||||
@@ -62,6 +62,7 @@
|
||||
"unmark",
|
||||
"unmoderated",
|
||||
"unparticipated",
|
||||
"unreads",
|
||||
"unsatisfieds",
|
||||
"unsnooze",
|
||||
"unsticky",
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/**
|
||||
* NOTICE OF LICENSE.
|
||||
*
|
||||
* UNIT3D Community Edition is open-sourced software licensed under the GNU Affero General Public License v3.0
|
||||
* The details is bundled with this project in the file LICENSE.txt.
|
||||
*
|
||||
* @project UNIT3D Community Edition
|
||||
*
|
||||
* @author Roardom <roardom@protonmail.com>
|
||||
* @license https://www.gnu.org/licenses/agpl-3.0.en.html/ GNU Affero General Public License v3.0
|
||||
*/
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class () extends Migration {
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('articles', function (Blueprint $table): void {
|
||||
$table->increments('id')->change();
|
||||
});
|
||||
|
||||
Schema::create('unread_articles', function (Blueprint $table): void {
|
||||
$table->increments('id');
|
||||
$table->unsignedInteger('article_id');
|
||||
$table->unsignedInteger('user_id');
|
||||
|
||||
$table->foreign('article_id')->references('id')->on('articles')->cascadeOnUpdate()->cascadeOnDelete();
|
||||
$table->foreign('user_id')->references('id')->on('users')->cascadeOnUpdate()->cascadeOnDelete();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,3 +1,9 @@
|
||||
.article-preview-wrapper.article-preview-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 2rem;
|
||||
}
|
||||
|
||||
.article-preview {
|
||||
padding: 18px;
|
||||
border-radius: 5px;
|
||||
@@ -32,6 +38,10 @@
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.article-preview__title svg {
|
||||
max-height: 15px;
|
||||
}
|
||||
|
||||
.article-preview__link {
|
||||
color: var(--article-card-head-fg);
|
||||
}
|
||||
|
||||
@@ -1,30 +1,38 @@
|
||||
@foreach ($articles as $article)
|
||||
<section class="panelV2 blocks__news" x-data="{ show: {{ $article->newNews }} }">
|
||||
<header class="panel__header" x-on:click="show = !show" style="cursor: pointer">
|
||||
<h2 class="panel__heading panel__heading--centered">
|
||||
@if ($article->newNews)
|
||||
@joypixels(':rotating_light:')
|
||||
{{ __('blocks.new-news') }} {{ $article->created_at->diffForHumans() }}
|
||||
@joypixels(':rotating_light:')
|
||||
@else
|
||||
{{ __('blocks.check-news') }} {{ $article->created_at->diffForHumans() }}
|
||||
@endif
|
||||
</h2>
|
||||
<div class="panel__actions">
|
||||
<div class="panel__action">
|
||||
<a
|
||||
href="{{ route('articles.index') }}"
|
||||
class="form__button form__button--text"
|
||||
>
|
||||
{{ __('common.view-all') }}
|
||||
</a>
|
||||
</div>
|
||||
<section
|
||||
class="panelV2 blocks__news"
|
||||
x-data="{
|
||||
show: {{ Js::from($articles->contains(fn ($article) => $article->unread_news_exists)) }},
|
||||
}"
|
||||
>
|
||||
<header class="panel__header" x-on:click="show = !show" style="cursor: pointer">
|
||||
<h2 class="panel__heading panel__heading--centered">
|
||||
@if ($articles->first()?->unread_news_exists)
|
||||
@joypixels(':rotating_light:')
|
||||
{{ __('blocks.new-news') }}
|
||||
{{ $articles->first()?->created_at?->diffForHumans() }}
|
||||
@joypixels(':rotating_light:')
|
||||
@else
|
||||
{{ __('blocks.check-news') }}
|
||||
{{ $articles->first()?->created_at?->diffForHumans() }}
|
||||
@endif
|
||||
</h2>
|
||||
<div class="panel__actions">
|
||||
<div class="panel__action">
|
||||
<a href="{{ route('articles.index') }}" class="form__button form__button--text">
|
||||
{{ __('common.view-all') }}
|
||||
</a>
|
||||
</div>
|
||||
</header>
|
||||
<div class="panel__body" x-cloak x-show="show">
|
||||
</div>
|
||||
</header>
|
||||
<div class="panel__body article-preview-wrapper" x-cloak x-show="show">
|
||||
@foreach ($articles as $article)
|
||||
<article class="article-preview">
|
||||
<header class="article-preview__header">
|
||||
<h2 class="article-preview__title">
|
||||
@if ($article->unread_news_exists)
|
||||
<x-animation.notification />
|
||||
@endif
|
||||
|
||||
<a
|
||||
class="article-preview__link"
|
||||
href="{{ route('articles.show', ['article' => $article]) }}"
|
||||
@@ -55,6 +63,6 @@
|
||||
{{ __('articles.read-more') }}
|
||||
</a>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
@endforeach
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user