mirror of
https://github.com/HDInnovations/UNIT3D-Community-Edition.git
synced 2026-05-24 05:29:26 -05:00
@@ -37,6 +37,7 @@ use App\Repositories\ChatRepository;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Notification;
|
||||
use Exception;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
/**
|
||||
* @see \Tests\Todo\Feature\Http\Controllers\PostControllerTest
|
||||
@@ -204,10 +205,6 @@ class PostController extends Controller
|
||||
*/
|
||||
public function update(Request $request, int $id): \Illuminate\Http\RedirectResponse
|
||||
{
|
||||
$request->validate([
|
||||
'content' => 'required|min:1',
|
||||
]);
|
||||
|
||||
$user = $request->user();
|
||||
|
||||
$post = Post::query()->findOrFail($id);
|
||||
@@ -217,9 +214,14 @@ class PostController extends Controller
|
||||
|
||||
abort_unless($post->topic()->authorized(canReplyTopic: true)->exists(), 403);
|
||||
|
||||
$post->update([
|
||||
'content' => $request->input('content'),
|
||||
]);
|
||||
$post->update($request->validate([
|
||||
'content' => 'required|min:1',
|
||||
'pinned' => [
|
||||
'sometimes',
|
||||
'boolean',
|
||||
Rule::excludeIf(!$user->group->is_modo),
|
||||
]
|
||||
]));
|
||||
|
||||
return redirect()->to($postUrl)
|
||||
->with('success', trans('forum.edit-post-success'));
|
||||
|
||||
@@ -77,11 +77,28 @@ class TopicPostSearch extends Component
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @var \Illuminate\Database\Eloquent\Collection<int, Post>
|
||||
*/
|
||||
final protected \Illuminate\Database\Eloquent\Collection $pinnedPosts {
|
||||
get => Post::query()
|
||||
->with('user.group')
|
||||
->withCount('likes', 'dislikes', 'authorPosts', 'authorTopics')
|
||||
->withSum('tips', 'bon')
|
||||
->where('topic_id', '=', $this->topic->id)
|
||||
->where('pinned', '=', true)
|
||||
->authorized(canReadTopic: true)
|
||||
->when($this->search !== '', fn ($query) => $query->where('content', 'LIKE', '%'.$this->search.'%'))
|
||||
->orderBy('created_at')
|
||||
->get();
|
||||
}
|
||||
|
||||
final public function render(): \Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View|\Illuminate\Contracts\Foundation\Application
|
||||
{
|
||||
return view('livewire.topic-post-search', [
|
||||
'topic' => $this->topic,
|
||||
'posts' => $this->posts,
|
||||
'topic' => $this->topic,
|
||||
'posts' => $this->posts,
|
||||
'pinnedPosts' => $this->pinnedPosts,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -29,6 +29,7 @@ use AllowDynamicProperties;
|
||||
* @property int $id
|
||||
* @property string $content
|
||||
* @property bool $anon
|
||||
* @property bool $pinned
|
||||
* @property \Illuminate\Support\Carbon|null $created_at
|
||||
* @property \Illuminate\Support\Carbon|null $updated_at
|
||||
* @property int $user_id
|
||||
@@ -50,6 +51,7 @@ final class Post extends Model
|
||||
protected $fillable = [
|
||||
'content',
|
||||
'anon',
|
||||
'pinned',
|
||||
'topic_id',
|
||||
'user_id',
|
||||
];
|
||||
@@ -62,7 +64,8 @@ final class Post extends Model
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'anon' => 'bool',
|
||||
'anon' => 'bool',
|
||||
'pinned' => 'bool',
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
<?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('posts', function (Blueprint $table): void {
|
||||
$table->boolean('pinned')->default(false)->after('anon');
|
||||
$table->index(['pinned', 'topic_id']);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1357,6 +1357,7 @@ CREATE TABLE `posts` (
|
||||
`id` int unsigned NOT NULL AUTO_INCREMENT,
|
||||
`content` text COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`anon` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`pinned` tinyint(1) NOT NULL DEFAULT '0',
|
||||
`created_at` timestamp NULL DEFAULT NULL,
|
||||
`updated_at` timestamp NULL DEFAULT NULL,
|
||||
`user_id` int unsigned NOT NULL,
|
||||
@@ -1365,6 +1366,7 @@ CREATE TABLE `posts` (
|
||||
KEY `fk_posts_topics1_idx` (`topic_id`),
|
||||
KEY `posts_created_at_index` (`created_at`),
|
||||
KEY `posts_user_id_foreign` (`user_id`),
|
||||
KEY `posts_pinned_topic_id_index` (`pinned`,`topic_id`),
|
||||
CONSTRAINT `posts_topic_id_foreign` FOREIGN KEY (`topic_id`) REFERENCES `topics` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT `posts_user_id_foreign` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON UPDATE CASCADE
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
@@ -3128,3 +3130,4 @@ INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (376,'2026_02_03_01
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (377,'2026_02_04_184040_combine_user_audibles_echoes',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (378,'2026_02_18_023757_change_donation_dates_to_timestamps',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (379,'2026_03_10_033755_migrate_request_bounty_created_at_to_updated_at',1);
|
||||
INSERT INTO `migrations` (`id`, `migration`, `batch`) VALUES (380,'2026_04_22_061705_add_pinned_column_to_posts',1);
|
||||
|
||||
@@ -21,6 +21,13 @@
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
.topic-posts__pinned-separator {
|
||||
width: 100%;
|
||||
border-width: 1px 0 0 0;
|
||||
border-style: solid;
|
||||
border-color: var(--post-pinned-separator-fg);
|
||||
}
|
||||
|
||||
.post {
|
||||
display: grid;
|
||||
grid-template-areas:
|
||||
@@ -38,16 +45,26 @@
|
||||
.post__header {
|
||||
grid-area: header;
|
||||
display: grid;
|
||||
grid-template-areas: 'datetime topic . tip-stats toolbar';
|
||||
grid-template-columns: auto auto 1fr auto auto;
|
||||
grid-template-areas: 'pin datetime topic . tip-stats toolbar';
|
||||
grid-template-columns: auto auto auto 1fr auto auto;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
font-size: 13px;
|
||||
color: var(--post-head-fg);
|
||||
background-color: var(--post-head-bg);
|
||||
padding-left: 9px;
|
||||
}
|
||||
|
||||
.post__pin,
|
||||
.post__datetime,
|
||||
.post__topic,
|
||||
.post__tip-stats {
|
||||
margin-right: 9px;
|
||||
}
|
||||
|
||||
.post__pin {
|
||||
grid-area: pin;
|
||||
}
|
||||
|
||||
.post__datetime {
|
||||
grid-area: datetime;
|
||||
}
|
||||
@@ -289,9 +306,15 @@
|
||||
}
|
||||
|
||||
.post__header {
|
||||
grid-template-areas: 'datetime topic . tip-stats overflow' '. . . . toolbar';
|
||||
grid-template-columns: auto auto 1fr auto auto;
|
||||
gap: 0 6px;
|
||||
grid-template-areas: 'pin datetime topic . tip-stats overflow' '. . . . . toolbar';
|
||||
grid-template-columns: auto auto auto 1fr auto auto;
|
||||
}
|
||||
|
||||
.post__pin,
|
||||
.post__datetime,
|
||||
.post__topic,
|
||||
.post__tip-stats {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.post__aside {
|
||||
|
||||
@@ -217,6 +217,7 @@
|
||||
--post-shadow: none;
|
||||
--post-head-fg: var(--text-color);
|
||||
--post-head-bg: none;
|
||||
--post-pinned-separator-fg: #404040;
|
||||
--post-toolbar-bg: #242424;
|
||||
--post-toolbar-fg: #b0b0b0;
|
||||
--post-toolbar-hover-bg: #2a2a2a;
|
||||
|
||||
@@ -268,6 +268,7 @@
|
||||
--post-shadow: none;
|
||||
--post-head-fg: var(--text-color);
|
||||
--post-head-bg: none;
|
||||
--post-pinned-separator-fg: #404040;
|
||||
--post-toolbar-bg: #1d1d1d;
|
||||
--post-toolbar-fg: #ccc;
|
||||
--post-toolbar-hover-bg: #262626;
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
0 1px 8px 0 rgba(0, 0, 0, 0.2);
|
||||
--post-head-fg: var(--panel-fg);
|
||||
--post-head-bg: none;
|
||||
--post-pinned-separator-fg: #aaa;
|
||||
--post-toolbar-bg: #989898;
|
||||
--post-toolbar-fg: #e5e5e5;
|
||||
--post-toolbar-hover-bg: #777;
|
||||
|
||||
@@ -229,6 +229,7 @@
|
||||
0 1px 8px 0 rgba(0, 0, 0, 0.2);
|
||||
--post-head-fg: var(--panel-fg);
|
||||
--post-head-bg: none;
|
||||
--post-pinned-separator-fg: #222;
|
||||
--post-toolbar-bg: #000;
|
||||
--post-toolbar-fg: #999;
|
||||
--post-toolbar-hover-bg: #000;
|
||||
|
||||
@@ -234,6 +234,7 @@
|
||||
0 1px 8px 0 rgba(0, 0, 0, 0.2);
|
||||
--post-head-fg: var(--panel-fg);
|
||||
--post-head-bg: none;
|
||||
--post-pinned-separator-fg: #444;
|
||||
--post-toolbar-bg: #343338;
|
||||
--post-toolbar-fg: #999;
|
||||
--post-toolbar-hover-bg: #343338;
|
||||
|
||||
@@ -233,6 +233,7 @@
|
||||
0 1px 8px 0 rgba(0, 0, 0, 0.2);
|
||||
--post-head-fg: var(--panel-fg);
|
||||
--post-head-bg: none;
|
||||
--post-pinned-separator-fg: #bbb;
|
||||
--post-toolbar-bg: #fff;
|
||||
--post-toolbar-fg: #777;
|
||||
--post-toolbar-hover-bg: #fff;
|
||||
|
||||
@@ -235,6 +235,7 @@
|
||||
0 1px 8px 0 rgba(0, 0, 0, 0.2);
|
||||
--post-head-fg: var(--panel-fg);
|
||||
--post-head-bg: none;
|
||||
--post-pinned-separator-fg: #222;
|
||||
--post-toolbar-bg: #0f111a;
|
||||
--post-toolbar-fg: #999;
|
||||
--post-toolbar-hover-bg: #0f111a;
|
||||
|
||||
@@ -247,6 +247,7 @@
|
||||
--post-shadow: none;
|
||||
--post-head-fg: var(--text-color);
|
||||
--post-head-bg: #232323;
|
||||
--post-pinned-separator-fg: #404040;
|
||||
--post-toolbar-bg: #232323;
|
||||
--post-toolbar-fg: #ccc;
|
||||
--post-toolbar-hover-bg: #262626;
|
||||
|
||||
@@ -11,6 +11,13 @@
|
||||
)"
|
||||
>
|
||||
<header class="post__header">
|
||||
@if ($post->pinned)
|
||||
<i
|
||||
class="{{ config('other.font-awesome') }} fa-thumbtack post__pin"
|
||||
title="Pinned"
|
||||
></i>
|
||||
@endif
|
||||
|
||||
<time
|
||||
class="post__datetime"
|
||||
datetime="{{ $post->created_at }}"
|
||||
|
||||
@@ -61,6 +61,24 @@
|
||||
@csrf
|
||||
@method('PATCH')
|
||||
@livewire('bbcode-input', ['name' => 'content', 'label' => __('forum.post'), 'content' => $post->content])
|
||||
|
||||
@if (auth()->user()->group->is_modo)
|
||||
<p class="form__group">
|
||||
<input type="hidden" name="pinned" value="0" />
|
||||
<input
|
||||
type="checkbox"
|
||||
class="form__checkbox"
|
||||
id="pinned"
|
||||
name="pinned"
|
||||
value="1"
|
||||
@checked(old('pinned') ?? $post->pinned)
|
||||
/>
|
||||
<label class="form__label" for="pinned">
|
||||
{{ __('forum.pin') }}
|
||||
</label>
|
||||
</p>
|
||||
@endif
|
||||
|
||||
<button class="form__button form__button--filled">
|
||||
{{ __('common.submit') }}
|
||||
</button>
|
||||
|
||||
@@ -58,11 +58,31 @@
|
||||
<div class="panel__body">
|
||||
@if ($posts->count() > 0)
|
||||
<ol class="topic-posts">
|
||||
@foreach ($pinnedPosts as $post)
|
||||
<li class="topic-posts__item">
|
||||
<x-forum.post :post="$post" />
|
||||
</li>
|
||||
@endforeach
|
||||
|
||||
@if ($pinnedPosts->isNotEmpty())
|
||||
<hr class="topic-posts__pinned-separator" />
|
||||
@endif
|
||||
|
||||
@foreach ($posts as $post)
|
||||
<li class="topic-posts__item">
|
||||
<x-forum.post :post="$post" />
|
||||
</li>
|
||||
@endforeach
|
||||
|
||||
@if ($pinnedPosts->isNotEmpty())
|
||||
<hr class="topic-posts__pinned-separator" />
|
||||
@endif
|
||||
|
||||
@foreach ($pinnedPosts as $post)
|
||||
<li class="topic-posts__item">
|
||||
<x-forum.post :post="$post" />
|
||||
</li>
|
||||
@endforeach
|
||||
</ol>
|
||||
@else
|
||||
No topics.
|
||||
|
||||
Reference in New Issue
Block a user