feat: forum rich-text editor, emoji picker, mentions, discover nav, feed, uploads, profile

Forum:
- TipTap WYSIWYG editor with full toolbar
- @emoji-mart/react emoji picker (consistent with tweets)
- @mention autocomplete with user search API
- Fix PHP 8.4 parse errors in Blade templates
- Fix thread data display (paginator items)
- Align forum page widths to max-w-5xl

Discover:
- Extract shared _nav.blade.php partial
- Add missing nav links to for-you page
- Add Following link for authenticated users

Feed/Posts:
- Post model, controllers, policies, migrations
- Feed page components (PostComposer, FeedCard, etc)
- Post reactions, comments, saves, reports, sharing
- Scheduled publishing support
- Link preview controller

Profile:
- Profile page components (ProfileHero, ProfileTabs)
- Profile API controller

Uploads:
- Upload wizard enhancements
- Scheduled publish picker
- Studio status bar and readiness checklist
This commit is contained in:
2026-03-03 09:48:31 +01:00
parent 1266f81d35
commit dc51d65440
178 changed files with 14308 additions and 665 deletions

View File

@@ -29,20 +29,31 @@ final class StudioArtworkQueryService
*/
public function list(int $userId, array $filters = [], int $perPage = 24): LengthAwarePaginator
{
// Skip Meilisearch when driver is null (e.g. in tests)
// Studio is a management dashboard — DB is always authoritative.
// Draft/unpublished artworks are never indexed in Meilisearch, so using
// Meili as the primary source would silently hide them.
//
// Meilisearch is only used when the user submits a free-text query (`q`),
// since it can provide relevance-ranked full-text search across many docs.
// Even then, we fall back to DB on any Meili error.
$hasTextQuery = !empty($filters['q']);
$driver = config('scout.driver');
if (empty($driver) || $driver === 'null') {
return $this->listViaDatabase($userId, $filters, $perPage);
$useMeili = $hasTextQuery && !empty($driver) && $driver !== 'null';
if ($useMeili) {
try {
return $this->listViaMeilisearch($userId, $filters, $perPage);
} catch (\Throwable $e) {
Log::warning('Studio: Meilisearch unavailable during text search, falling back to DB', [
'error' => $e->getMessage(),
'user_id' => $userId,
]);
// fall through to DB
}
}
try {
return $this->listViaMeilisearch($userId, $filters, $perPage);
} catch (\Throwable $e) {
Log::warning('Studio: Meilisearch unavailable, falling back to DB', [
'error' => $e->getMessage(),
]);
return $this->listViaDatabase($userId, $filters, $perPage);
}
return $this->listViaDatabase($userId, $filters, $perPage);
}
private function listViaMeilisearch(int $userId, array $filters, int $perPage): LengthAwarePaginator