diff --git a/app/Console/Commands/PopulateMemeMediaSlugs.php b/app/Console/Commands/PopulateMemeMediaSlugs.php new file mode 100644 index 0000000..9bd9d54 --- /dev/null +++ b/app/Console/Commands/PopulateMemeMediaSlugs.php @@ -0,0 +1,51 @@ +info('Starting to populate MemeMedia slugs...'); + + $memes = MemeMedia::whereNull('slug')->get(); + + if ($memes->isEmpty()) { + $this->info('No memes found without slugs.'); + + return; + } + + $this->info("Found {$memes->count()} memes without slugs."); + + $bar = $this->output->createProgressBar($memes->count()); + $bar->start(); + + foreach ($memes as $meme) { + $baseSlug = Str::slug($meme->name); + $slug = $baseSlug; + $counter = 1; + + // Ensure slug is unique + while (MemeMedia::where('slug', $slug)->exists()) { + $slug = $baseSlug.'-'.$counter; + $counter++; + } + + $meme->update(['slug' => $slug]); + $bar->advance(); + } + + $bar->finish(); + $this->newLine(); + $this->info('Successfully populated all MemeMedia slugs!'); + } +} diff --git a/app/Http/Controllers/FrontMemeController.php b/app/Http/Controllers/FrontMemeController.php new file mode 100644 index 0000000..e08210d --- /dev/null +++ b/app/Http/Controllers/FrontMemeController.php @@ -0,0 +1,98 @@ +getMemes($request->input('search')); + } + + public function search(string $search): Response + { + // Convert + back to spaces for search + $searchTerm = str_replace('+', ' ', $search); + return $this->getMemes($searchTerm); + } + + private function getMemes(?string $search = null): Response + { + $query = MemeMedia::query() + ->where('is_enabled', true) + ->orderBy('created_at', 'desc'); + + // Search functionality + if ($search) { + $query->where(function ($q) use ($search) { + $q->where('name', 'ilike', "%{$search}%") + ->orWhere('description', 'ilike', "%{$search}%") + ->orWhereJsonContains('keywords', $search) + ->orWhereJsonContains('action_keywords', $search) + ->orWhereJsonContains('emotion_keywords', $search) + ->orWhereJsonContains('misc_keywords', $search); + }); + } + + $memes = $query->paginate(12); + + // Get available types for filter + $types = MemeMedia::where('is_enabled', true) + ->distinct() + ->pluck('type') + ->filter(); + + // Get popular keywords for filter + $popularKeywords = MemeMedia::where('is_enabled', true) + ->get() + ->pluck('keywords') + ->flatten() + ->countBy() + ->sort() + ->reverse() + ->take(20) + ->keys(); + + return Inertia::render('memes/index', [ + 'memes' => $memes, + 'types' => $types, + 'popularKeywords' => $popularKeywords, + 'filters' => [ + 'search' => $search, + ], + ]); + } + + public function show(string $slug): Response + { + $meme = MemeMedia::where('slug', $slug) + ->where('is_enabled', true) + ->firstOrFail(); + + // Get related memes based on similar keywords + $relatedMemes = MemeMedia::where('is_enabled', true) + ->where('id', '!=', $meme->id) + ->where(function ($query) use ($meme) { + if ($meme->keywords) { + foreach ($meme->keywords as $keyword) { + $query->orWhereJsonContains('keywords', $keyword) + ->orWhereJsonContains('action_keywords', $keyword) + ->orWhereJsonContains('emotion_keywords', $keyword) + ->orWhereJsonContains('misc_keywords', $keyword); + } + } + }) + ->limit(6) + ->get(); + + return Inertia::render('memes/show', [ + 'meme' => $meme, + 'relatedMemes' => $relatedMemes, + ]); + } +} diff --git a/app/Models/MemeMedia.php b/app/Models/MemeMedia.php index 66f599e..f1f92a4 100644 --- a/app/Models/MemeMedia.php +++ b/app/Models/MemeMedia.php @@ -53,6 +53,7 @@ class MemeMedia extends Model 'sub_type', 'original_id', 'name', + 'slug', 'description', 'keywords', 'group', diff --git a/database/migrations/2025_07_16_084731_add_slug_to_meme_medias_table.php b/database/migrations/2025_07_16_084731_add_slug_to_meme_medias_table.php new file mode 100644 index 0000000..7f2ed61 --- /dev/null +++ b/database/migrations/2025_07_16_084731_add_slug_to_meme_medias_table.php @@ -0,0 +1,30 @@ +string('slug')->nullable()->after('name'); + $table->index('slug'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('meme_medias', function (Blueprint $table) { + $table->dropIndex(['slug']); + $table->dropColumn('slug'); + }); + } +}; diff --git a/resources/js/components/ui/keyword-badge.tsx b/resources/js/components/ui/keyword-badge.tsx new file mode 100644 index 0000000..4a09d6f --- /dev/null +++ b/resources/js/components/ui/keyword-badge.tsx @@ -0,0 +1,24 @@ +import { Link } from '@inertiajs/react'; +import { route } from 'ziggy-js'; + +interface KeywordBadgeProps { + keyword: string; + size?: 'default' | 'lg'; + className?: string; +} + +const sizeClasses = { + default: 'px-2.5 py-0.5 text-xs', + lg: 'px-3 py-1 text-sm', +}; + +export function KeywordBadge({ keyword, size = 'default', className = '' }: KeywordBadgeProps) { + return ( + + {keyword} + + ); +} \ No newline at end of file diff --git a/resources/js/modules/editor/partials/editor-header.jsx b/resources/js/modules/editor/partials/editor-header.jsx index e742b82..bd483bf 100644 --- a/resources/js/modules/editor/partials/editor-header.jsx +++ b/resources/js/modules/editor/partials/editor-header.jsx @@ -1,4 +1,4 @@ -import { cn } from '@/lib/utils'; +import BrandLogo from '@/pages/home/partials/BrandLogo'; import { useMitt } from '@/plugins/MittContext'; import useLocalSettingsStore from '@/stores/localSettingsStore'; @@ -12,14 +12,7 @@ const EditorHeader = ({ className = '', onNavClick = () => {}, isNavActive = fal }; return ( -
- MEMEFA.ST LOGO - -
- MEME - FAST -
-
+ //
// + + + {/* Popular Keywords */} +
+

Popular Keywords

+
+ {popularKeywords.map((keyword) => ( + + ))} +
+
+ + {/* Active Search */} + {filters.search && ( +
+ + Search: {filters.search} + + +
+ )} + + + + {/* Results Count */} +
+

+ Showing {memes.data.length} of {memes.total} memes +

+
+ + {/* Memes Grid */} +
+ {memes.data.map((meme) => ( + +
+ {meme.name} + + + +
+
+

{meme.name}

+
+ {meme.keywords?.slice(0, 6).map((keyword, index) => )} + {meme.keywords && meme.keywords.length > 6 && ( + + +{meme.keywords.length - 6} more + + )} +
+
+ + + +
+
+
+ ))} +
+ + {/* Pagination */} + {memes.last_page > 1 && ( +
+ {memes.links.map((link, index) => ( +
+ )} +
+