Compare commits

...

11 Commits

25 changed files with 1179 additions and 93 deletions

View File

@@ -57,3 +57,8 @@ VITE_PUSHER_HOST="${PUSHER_HOST}"
VITE_PUSHER_PORT="${PUSHER_PORT}" VITE_PUSHER_PORT="${PUSHER_PORT}"
VITE_PUSHER_SCHEME="${PUSHER_SCHEME}" VITE_PUSHER_SCHEME="${PUSHER_SCHEME}"
VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" VITE_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}"
SCOUT_QUEUE=true
SCOUT_DRIVER=meilisearch
MEILISEARCH_HOST=http://127.0.0.1:7700
MEILISEARCH_KEY=masterKey

View File

@@ -28,6 +28,10 @@ apt install php8.3-fpm
apt install mariadb-server apt install mariadb-server
sudo mysql_secure_installation sudo mysql_secure_installation
# Install Meilisearch
echo "deb [trusted=yes] https://apt.fury.io/meilisearch/ /" | sudo tee /etc/apt/sources.list.d/fury.list
sudo apt update && sudo apt install meilisearch
# Clone Repo # Clone Repo
cd /var/www cd /var/www
git clone https://gitea.hstream.moe/w33b/hstream.git git clone https://gitea.hstream.moe/w33b/hstream.git

View File

@@ -0,0 +1,16 @@
<?php
namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
class CommentsController extends Controller
{
/**
* Display Comments Page.
*/
public function index(): \Illuminate\View\View
{
return view('admin.comments.index');
}
}

View File

@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
use App\Models\Hentai; use App\Models\Hentai;
use App\Models\PopularMonthly; use App\Models\PopularMonthly;
use Carbon\Carbon;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
@@ -46,6 +47,8 @@ class HentaiApiController extends Controller
// Cache for 60 minutes // Cache for 60 minutes
$data = Cache::remember('api_monthly_views', now()->addMinutes(60), function () { $data = Cache::remember('api_monthly_views', now()->addMinutes(60), function () {
return PopularMonthly::selectRaw('DATE(created_at) as date, COUNT(*) as count') return PopularMonthly::selectRaw('DATE(created_at) as date, COUNT(*) as count')
->whereDate('created_at', '<', Carbon::today())
->whereDate('created_at', '>=', Carbon::today()->subDays(28))
->groupBy('date') ->groupBy('date')
->orderBy('date', 'asc') ->orderBy('date', 'asc')
->get(); ->get();

View File

@@ -52,6 +52,22 @@ class HomeController extends Controller
return view('auth.banned'); return view('auth.banned');
} }
/**
* Redirects to a random Hentai episode
* Done due to performance reasons
*/
public function random(): \Illuminate\Http\RedirectResponse
{
$random = Episode::inRandomOrder()
->limit(1)
->pluck('slug')
->first();
return redirect()->route('hentai.index', [
'title' => $random,
]);
}
/** /**
* Display Search Page. * Display Search Page.
*/ */

View File

@@ -0,0 +1,40 @@
<?php
namespace App\Livewire;
use Livewire\Component;
use Livewire\WithPagination;
use Illuminate\Support\Facades\DB;
class AdminCommentSearch extends Component
{
use WithPagination;
public $search = '';
public $userSearch = '';
public function updatingSearch(): void
{
$this->resetPage();
}
public function updatingUserSearch(): void
{
$this->resetPage();
}
public function render()
{
$comments = DB::table('comments')
->join('users', 'comments.commenter_id', '=', 'users.id')
->select('comments.*', 'users.username')
->when($this->search !== '', fn ($query) => $query->where('comment', 'LIKE', "%$this->search%"))
->when($this->userSearch !== '', fn ($query) => $query->where('username', 'LIKE', "%$this->userSearch%"))
->paginate(12);
return view('livewire.admin-comment-search', [
'comments' => $comments
]);
}
}

View File

@@ -8,6 +8,8 @@ use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
use Livewire\Attributes\Url; use Livewire\Attributes\Url;
use Illuminate\Support\Facades\DB;
class AdminUserSearch extends Component class AdminUserSearch extends Component
{ {
use WithPagination; use WithPagination;
@@ -24,6 +26,18 @@ class AdminUserSearch extends Component
#[Url(history: true)] #[Url(history: true)]
public $banned = []; public $banned = [];
public function deleteUserComments(int $userID)
{
$user = User::where('id', $userID)
->firstOrFail();
DB::table('comments')
->where('commenter_id', '=', $user->id)
->delete();
cache()->flush();
}
public function render() public function render()
{ {
$users = User::when($this->filtered !== [], fn ($query) => $query->where('id', '>=', 10000)) $users = User::when($this->filtered !== [], fn ($query) => $query->where('id', '>=', 10000))

View File

@@ -5,15 +5,37 @@ namespace App\Livewire;
use App\Models\Downloads; use App\Models\Downloads;
use Livewire\Component; use Livewire\Component;
use Livewire\WithPagination; use Livewire\WithPagination;
use Livewire\Attributes\Url;
class DownloadsSearch extends Component class DownloadsSearch extends Component
{ {
use WithPagination; use WithPagination;
#[Url(history: true)]
public $fileSearch; public $fileSearch;
public $order = 'created_at_desc'; public $order = 'created_at_desc';
public $options = [
'FHD' => true,
'FHD 48fps' => true,
];
public $isOpen = false;
// To toggle individual option selection
public function toggleOption($option)
{
$this->options[$option] = !$this->options[$option];
$this->resetPage();
}
// To toggle dropdown visibility
public function toggleDropdown()
{
$this->isOpen = !$this->isOpen;
}
protected $queryString = [ protected $queryString = [
'fileSearch' => ['except' => '', 'as' => 'fS'], 'fileSearch' => ['except' => '', 'as' => 'fS'],
'order' => ['except' => '', 'as' => 'order'], 'order' => ['except' => '', 'as' => 'order'],
@@ -24,6 +46,29 @@ class DownloadsSearch extends Component
$this->resetPage(); $this->resetPage();
} }
// Map the selected options to database types
private function getSelectedTypes()
{
$types = [];
// Map the options to their corresponding database values
foreach ($this->options as $label => $selected) {
if ($selected) {
if ($label === 'FHD') {
$types[] = 'FHD';
} elseif ($label === 'FHD 48fps') {
$types[] = 'FHDi';
} elseif ($label === 'UHD' && auth()->user()->is_patreon) {
$types[] = 'UHD';
} elseif ($label === 'UHD 48fps' && auth()->user()->is_patreon) {
$types[] = 'UHDi';
}
}
}
return $types;
}
public function clicked($downloadId) public function clicked($downloadId)
{ {
$download = Downloads::find($downloadId); $download = Downloads::find($downloadId);
@@ -36,6 +81,17 @@ class DownloadsSearch extends Component
cache()->forget("episode_{$download->episode->id}_download_{$download->type}"); cache()->forget("episode_{$download->episode->id}_download_{$download->type}");
} }
public function mount()
{
if (!auth()->user()->is_patreon) {
return;
}
// Add patreon options
$this->options['UHD'] = true;
$this->options['UHD 48fps'] = true;
}
public function render() public function render()
{ {
$orderby = 'created_at'; $orderby = 'created_at';
@@ -72,7 +128,7 @@ class DownloadsSearch extends Component
} }
$downloads = Downloads::when($this->fileSearch != '', fn ($query) => $query->where('url', 'like', '%'.$this->fileSearch.'%')) $downloads = Downloads::when($this->fileSearch != '', fn ($query) => $query->where('url', 'like', '%'.$this->fileSearch.'%'))
->when(!auth()->user()->is_patreon, fn ($query) => $query->whereIn('type', ['FHD', 'FHDi'])) ->whereIn('type', $this->getSelectedTypes())
->whereNotNull('size') ->whereNotNull('size')
->orderBy($orderby, $orderdirection) ->orderBy($orderby, $orderdirection)
->paginate(20); ->paginate(20);

View File

@@ -3,7 +3,6 @@
namespace App\Livewire; namespace App\Livewire;
use App\Models\Episode; use App\Models\Episode;
use App\Models\Gallery;
use Livewire\Component; use Livewire\Component;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
@@ -18,22 +17,15 @@ class NavLiveSearch extends Component
public function render() public function render()
{ {
$episodes = []; $episodes = [];
$randomimage = null;
if ($this->navSearch != '') { if ($this->navSearch != '') {
$episodes = Episode::with('gallery')->where('title', 'like', '%'.$this->navSearch.'%') $episodes = Episode::search($this->navSearch)
->orWhere('title_jpn', 'like', '%'.$this->navSearch.'%') ->when(Auth::guest(), fn ($query) => $query->whereNotIn('tags', ['Loli', 'Shota']))
->when(Auth::guest(), fn ($query) => $query->withoutTags(['loli', 'shota'])) ->take(7)
->take(10)
->get(); ->get();
$randomimage = Gallery::all()
->random(1)
->first();
} }
return view('livewire.nav-live-search', [ return view('livewire.nav-live-search', [
'episodes' => $episodes, 'episodes' => $episodes,
'randomimage' => $randomimage,
'query' => $this->navSearch, 'query' => $this->navSearch,
'hide' => empty($this->navSearch), 'hide' => empty($this->navSearch),
]); ]);

View File

@@ -9,6 +9,7 @@ use App\Models\PopularDaily;
use Conner\Tagging\Taggable; use Conner\Tagging\Taggable;
use Laravelista\Comments\Commentable; use Laravelista\Comments\Commentable;
use Laravel\Scout\Searchable;
use Maize\Markable\Markable; use Maize\Markable\Markable;
use Maize\Markable\Models\Like; use Maize\Markable\Models\Like;
@@ -26,11 +27,40 @@ class Episode extends Model implements Sitemapable
{ {
use Commentable, Markable, Taggable; use Commentable, Markable, Taggable;
use HasFactory; use HasFactory;
use Searchable;
protected static $marks = [ protected static $marks = [
Like::class Like::class
]; ];
/**
* Get the name of the index associated with the model.
*/
public function searchableAs(): string
{
return 'episodes_index';
}
/**
* Get the indexable data array for the model.
*
* @return array<string, mixed>
*/
public function toSearchableArray()
{
return [
'title' => $this->title,
'title_search' => $this->title_search,
'title_jpn' => $this->title_jpn,
'slug' => $this->slug,
'description' => $this->description,
'view_count' => $this->view_count,
'tags' => $this->tagNames(),
'release_date' => $this->release_date,
'created_at' => $this->created_at,
];
}
/** /**
* Get the studio for the Hentai. * Get the studio for the Hentai.
*/ */

View File

@@ -11,15 +11,18 @@
"php": "^8.2", "php": "^8.2",
"guzzlehttp/guzzle": "^7.8.1", "guzzlehttp/guzzle": "^7.8.1",
"hisorange/browser-detect": "^5.0", "hisorange/browser-detect": "^5.0",
"http-interop/http-factory-guzzle": "^1.2",
"intervention/image": "^3.9", "intervention/image": "^3.9",
"intervention/image-laravel": "^1.3", "intervention/image-laravel": "^1.3",
"jakyeru/larascord": "^6.0", "jakyeru/larascord": "^6.0",
"laravel/framework": "^11.0", "laravel/framework": "^11.0",
"laravel/sanctum": "^4.0", "laravel/sanctum": "^4.0",
"laravel/scout": "^10.20",
"laravel/tinker": "^2.10", "laravel/tinker": "^2.10",
"laravelista/comments": "dev-l11-compatibility", "laravelista/comments": "dev-l11-compatibility",
"livewire/livewire": "^3.6.4", "livewire/livewire": "^3.6.4",
"maize-tech/laravel-markable": "^2.3.0", "maize-tech/laravel-markable": "^2.3.0",
"meilisearch/meilisearch-php": "^1.16",
"mews/captcha": "3.4.4", "mews/captcha": "3.4.4",
"predis/predis": "^2.2", "predis/predis": "^2.2",
"realrashid/sweet-alert": "^7.2", "realrashid/sweet-alert": "^7.2",

380
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "0a70102075d514ad96b36656bb447aa3", "content-hash": "484d21a7c10b1609a22d642e71a71cc3",
"packages": [ "packages": [
{ {
"name": "brick/math", "name": "brick/math",
@@ -1247,6 +1247,64 @@
}, },
"time": "2024-02-05T08:21:06+00:00" "time": "2024-02-05T08:21:06+00:00"
}, },
{
"name": "http-interop/http-factory-guzzle",
"version": "1.2.0",
"source": {
"type": "git",
"url": "https://github.com/http-interop/http-factory-guzzle.git",
"reference": "8f06e92b95405216b237521cc64c804dd44c4a81"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/http-interop/http-factory-guzzle/zipball/8f06e92b95405216b237521cc64c804dd44c4a81",
"reference": "8f06e92b95405216b237521cc64c804dd44c4a81",
"shasum": ""
},
"require": {
"guzzlehttp/psr7": "^1.7||^2.0",
"php": ">=7.3",
"psr/http-factory": "^1.0"
},
"provide": {
"psr/http-factory-implementation": "^1.0"
},
"require-dev": {
"http-interop/http-factory-tests": "^0.9",
"phpunit/phpunit": "^9.5"
},
"suggest": {
"guzzlehttp/psr7": "Includes an HTTP factory starting in version 2.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Http\\Factory\\Guzzle\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "An HTTP Factory using Guzzle PSR7",
"keywords": [
"factory",
"http",
"psr-17",
"psr-7"
],
"support": {
"issues": "https://github.com/http-interop/http-factory-guzzle/issues",
"source": "https://github.com/http-interop/http-factory-guzzle/tree/1.2.0"
},
"time": "2021-07-21T13:50:14+00:00"
},
{ {
"name": "intervention/gif", "name": "intervention/gif",
"version": "4.2.2", "version": "4.2.2",
@@ -1979,6 +2037,87 @@
}, },
"time": "2025-07-09T19:45:24+00:00" "time": "2025-07-09T19:45:24+00:00"
}, },
{
"name": "laravel/scout",
"version": "v10.20.0",
"source": {
"type": "git",
"url": "https://github.com/laravel/scout.git",
"reference": "a04d7a8eb27b66c8b7edb7e0c6a078e9e78c4f5b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/scout/zipball/a04d7a8eb27b66c8b7edb7e0c6a078e9e78c4f5b",
"reference": "a04d7a8eb27b66c8b7edb7e0c6a078e9e78c4f5b",
"shasum": ""
},
"require": {
"illuminate/bus": "^9.0|^10.0|^11.0|^12.0",
"illuminate/contracts": "^9.0|^10.0|^11.0|^12.0",
"illuminate/database": "^9.0|^10.0|^11.0|^12.0",
"illuminate/http": "^9.0|^10.0|^11.0|^12.0",
"illuminate/pagination": "^9.0|^10.0|^11.0|^12.0",
"illuminate/queue": "^9.0|^10.0|^11.0|^12.0",
"illuminate/support": "^9.0|^10.0|^11.0|^12.0",
"php": "^8.0",
"symfony/console": "^6.0|^7.0"
},
"conflict": {
"algolia/algoliasearch-client-php": "<3.2.0|>=5.0.0"
},
"require-dev": {
"algolia/algoliasearch-client-php": "^3.2|^4.0",
"meilisearch/meilisearch-php": "^1.0",
"mockery/mockery": "^1.0",
"orchestra/testbench": "^7.31|^8.11|^9.0|^10.0",
"php-http/guzzle7-adapter": "^1.0",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9.3|^10.4|^11.5",
"typesense/typesense-php": "^4.9.3"
},
"suggest": {
"algolia/algoliasearch-client-php": "Required to use the Algolia engine (^3.2).",
"meilisearch/meilisearch-php": "Required to use the Meilisearch engine (^1.0).",
"typesense/typesense-php": "Required to use the Typesense engine (^4.9)."
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Laravel\\Scout\\ScoutServiceProvider"
]
},
"branch-alias": {
"dev-master": "10.x-dev"
}
},
"autoload": {
"psr-4": {
"Laravel\\Scout\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
}
],
"description": "Laravel Scout provides a driver based solution to searching your Eloquent models.",
"keywords": [
"algolia",
"laravel",
"search"
],
"support": {
"issues": "https://github.com/laravel/scout/issues",
"source": "https://github.com/laravel/scout"
},
"time": "2025-10-14T14:09:26+00:00"
},
{ {
"name": "laravel/serializable-closure", "name": "laravel/serializable-closure",
"version": "v2.0.5", "version": "v2.0.5",
@@ -3069,6 +3208,86 @@
}, },
"time": "2025-08-20T17:20:16+00:00" "time": "2025-08-20T17:20:16+00:00"
}, },
{
"name": "meilisearch/meilisearch-php",
"version": "v1.16.1",
"source": {
"type": "git",
"url": "https://github.com/meilisearch/meilisearch-php.git",
"reference": "f9f63e0e7d12ffaae54f7317fa8f4f4dfa8ae7b6"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/meilisearch/meilisearch-php/zipball/f9f63e0e7d12ffaae54f7317fa8f4f4dfa8ae7b6",
"reference": "f9f63e0e7d12ffaae54f7317fa8f4f4dfa8ae7b6",
"shasum": ""
},
"require": {
"ext-json": "*",
"php": "^7.4 || ^8.0",
"php-http/discovery": "^1.7",
"psr/http-client": "^1.0",
"symfony/polyfill-php81": "^1.33"
},
"require-dev": {
"http-interop/http-factory-guzzle": "^1.2.0",
"php-cs-fixer/shim": "^3.59.3",
"phpstan/phpstan": "^2.0",
"phpstan/phpstan-deprecation-rules": "^2.0",
"phpstan/phpstan-phpunit": "^2.0",
"phpstan/phpstan-strict-rules": "^2.0",
"phpunit/phpunit": "^9.5 || ^10.5",
"symfony/http-client": "^5.4|^6.0|^7.0"
},
"suggest": {
"guzzlehttp/guzzle": "Use Guzzle ^7 as HTTP client",
"http-interop/http-factory-guzzle": "Factory for guzzlehttp/guzzle",
"symfony/http-client": "Use Symfony Http client"
},
"type": "library",
"autoload": {
"psr-4": {
"MeiliSearch\\": "src/",
"Meilisearch\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Clémentine Urquizar",
"email": "clementine@meilisearch.com"
},
{
"name": "Bruno Casali",
"email": "bruno@meilisearch.com"
},
{
"name": "Laurent Cazanove",
"email": "lau.cazanove@gmail.com"
},
{
"name": "Tomas Norkūnas",
"email": "norkunas.tom@gmail.com"
}
],
"description": "PHP wrapper for the Meilisearch API",
"keywords": [
"api",
"client",
"instant",
"meilisearch",
"php",
"search"
],
"support": {
"issues": "https://github.com/meilisearch/meilisearch-php/issues",
"source": "https://github.com/meilisearch/meilisearch-php/tree/v1.16.1"
},
"time": "2025-09-18T10:15:45+00:00"
},
{ {
"name": "mews/captcha", "name": "mews/captcha",
"version": "3.4.4", "version": "3.4.4",
@@ -3819,6 +4038,85 @@
], ],
"time": "2025-05-08T08:14:37+00:00" "time": "2025-05-08T08:14:37+00:00"
}, },
{
"name": "php-http/discovery",
"version": "1.20.0",
"source": {
"type": "git",
"url": "https://github.com/php-http/discovery.git",
"reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-http/discovery/zipball/82fe4c73ef3363caed49ff8dd1539ba06044910d",
"reference": "82fe4c73ef3363caed49ff8dd1539ba06044910d",
"shasum": ""
},
"require": {
"composer-plugin-api": "^1.0|^2.0",
"php": "^7.1 || ^8.0"
},
"conflict": {
"nyholm/psr7": "<1.0",
"zendframework/zend-diactoros": "*"
},
"provide": {
"php-http/async-client-implementation": "*",
"php-http/client-implementation": "*",
"psr/http-client-implementation": "*",
"psr/http-factory-implementation": "*",
"psr/http-message-implementation": "*"
},
"require-dev": {
"composer/composer": "^1.0.2|^2.0",
"graham-campbell/phpspec-skip-example-extension": "^5.0",
"php-http/httplug": "^1.0 || ^2.0",
"php-http/message-factory": "^1.0",
"phpspec/phpspec": "^5.1 || ^6.1 || ^7.3",
"sebastian/comparator": "^3.0.5 || ^4.0.8",
"symfony/phpunit-bridge": "^6.4.4 || ^7.0.1"
},
"type": "composer-plugin",
"extra": {
"class": "Http\\Discovery\\Composer\\Plugin",
"plugin-optional": true
},
"autoload": {
"psr-4": {
"Http\\Discovery\\": "src/"
},
"exclude-from-classmap": [
"src/Composer/Plugin.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com"
}
],
"description": "Finds and installs PSR-7, PSR-17, PSR-18 and HTTPlug implementations",
"homepage": "http://php-http.org",
"keywords": [
"adapter",
"client",
"discovery",
"factory",
"http",
"message",
"psr17",
"psr7"
],
"support": {
"issues": "https://github.com/php-http/discovery/issues",
"source": "https://github.com/php-http/discovery/tree/1.20.0"
},
"time": "2024-10-02T11:20:13+00:00"
},
{ {
"name": "phpoption/phpoption", "name": "phpoption/phpoption",
"version": "1.9.4", "version": "1.9.4",
@@ -6963,6 +7261,86 @@
], ],
"time": "2025-01-02T08:10:11+00:00" "time": "2025-01-02T08:10:11+00:00"
}, },
{
"name": "symfony/polyfill-php81",
"version": "v1.33.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php81.git",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php81/zipball/4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"reference": "4a4cfc2d253c21a5ad0e53071df248ed48c6ce5c",
"shasum": ""
},
"require": {
"php": ">=7.2"
},
"type": "library",
"extra": {
"thanks": {
"url": "https://github.com/symfony/polyfill",
"name": "symfony/polyfill"
}
},
"autoload": {
"files": [
"bootstrap.php"
],
"psr-4": {
"Symfony\\Polyfill\\Php81\\": ""
},
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.1+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"support": {
"source": "https://github.com/symfony/polyfill-php81/tree/v1.33.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://github.com/nicolas-grekas",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2024-09-09T11:45:10+00:00"
},
{ {
"name": "symfony/polyfill-php83", "name": "symfony/polyfill-php83",
"version": "v1.33.0", "version": "v1.33.0",

224
config/scout.php Normal file
View File

@@ -0,0 +1,224 @@
<?php
use App\Models\Episode;
return [
/*
|--------------------------------------------------------------------------
| Default Search Engine
|--------------------------------------------------------------------------
|
| This option controls the default search connection that gets used while
| using Laravel Scout. This connection is used when syncing all models
| to the search service. You should adjust this based on your needs.
|
| Supported: "algolia", "meilisearch", "typesense",
| "database", "collection", "null"
|
*/
'driver' => env('SCOUT_DRIVER', 'collection'),
/*
|--------------------------------------------------------------------------
| Index Prefix
|--------------------------------------------------------------------------
|
| Here you may specify a prefix that will be applied to all search index
| names used by Scout. This prefix may be useful if you have multiple
| "tenants" or applications sharing the same search infrastructure.
|
*/
'prefix' => env('SCOUT_PREFIX', ''),
/*
|--------------------------------------------------------------------------
| Queue Data Syncing
|--------------------------------------------------------------------------
|
| This option allows you to control if the operations that sync your data
| with your search engines are queued. When this is set to "true" then
| all automatic data syncing will get queued for better performance.
|
*/
'queue' => env('SCOUT_QUEUE', false),
/*
|--------------------------------------------------------------------------
| Database Transactions
|--------------------------------------------------------------------------
|
| This configuration option determines if your data will only be synced
| with your search indexes after every open database transaction has
| been committed, thus preventing any discarded data from syncing.
|
*/
'after_commit' => false,
/*
|--------------------------------------------------------------------------
| Chunk Sizes
|--------------------------------------------------------------------------
|
| These options allow you to control the maximum chunk size when you are
| mass importing data into the search engine. This allows you to fine
| tune each of these chunk sizes based on the power of the servers.
|
*/
'chunk' => [
'searchable' => 500,
'unsearchable' => 500,
],
/*
|--------------------------------------------------------------------------
| Soft Deletes
|--------------------------------------------------------------------------
|
| This option allows to control whether to keep soft deleted records in
| the search indexes. Maintaining soft deleted records can be useful
| if your application still needs to search for the records later.
|
*/
'soft_delete' => false,
/*
|--------------------------------------------------------------------------
| Identify User
|--------------------------------------------------------------------------
|
| This option allows you to control whether to notify the search engine
| of the user performing the search. This is sometimes useful if the
| engine supports any analytics based on this application's users.
|
| Supported engines: "algolia"
|
*/
'identify' => env('SCOUT_IDENTIFY', false),
/*
|--------------------------------------------------------------------------
| Algolia Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your Algolia settings. Algolia is a cloud hosted
| search engine which works great with Scout out of the box. Just plug
| in your application ID and admin API key to get started searching.
|
*/
'algolia' => [
'id' => env('ALGOLIA_APP_ID', ''),
'secret' => env('ALGOLIA_SECRET', ''),
'index-settings' => [
// 'users' => [
// 'searchableAttributes' => ['id', 'name', 'email'],
// 'attributesForFaceting'=> ['filterOnly(email)'],
// ],
],
],
/*
|--------------------------------------------------------------------------
| Meilisearch Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your Meilisearch settings. Meilisearch is an open
| source search engine with minimal configuration. Below, you can state
| the host and key information for your own Meilisearch installation.
|
| See: https://www.meilisearch.com/docs/learn/configuration/instance_options#all-instance-options
|
*/
'meilisearch' => [
'host' => env('MEILISEARCH_HOST', 'http://localhost:7700'),
'key' => env('MEILISEARCH_KEY'),
'index-settings' => [
Episode::class => [
'filterableAttributes' => [
'title',
'title_search',
'title_jpn',
'slug',
'description',
'tags'
],
'sortableAttributes' => [
'created_at',
'release_date',
'view_count',
'title'
],
],
],
],
/*
|--------------------------------------------------------------------------
| Typesense Configuration
|--------------------------------------------------------------------------
|
| Here you may configure your Typesense settings. Typesense is an open
| source search engine using minimal configuration. Below, you will
| state the host, key, and schema configuration for the instance.
|
*/
'typesense' => [
'client-settings' => [
'api_key' => env('TYPESENSE_API_KEY', 'xyz'),
'nodes' => [
[
'host' => env('TYPESENSE_HOST', 'localhost'),
'port' => env('TYPESENSE_PORT', '8108'),
'path' => env('TYPESENSE_PATH', ''),
'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
],
],
'nearest_node' => [
'host' => env('TYPESENSE_HOST', 'localhost'),
'port' => env('TYPESENSE_PORT', '8108'),
'path' => env('TYPESENSE_PATH', ''),
'protocol' => env('TYPESENSE_PROTOCOL', 'http'),
],
'connection_timeout_seconds' => env('TYPESENSE_CONNECTION_TIMEOUT_SECONDS', 2),
'healthcheck_interval_seconds' => env('TYPESENSE_HEALTHCHECK_INTERVAL_SECONDS', 30),
'num_retries' => env('TYPESENSE_NUM_RETRIES', 3),
'retry_interval_seconds' => env('TYPESENSE_RETRY_INTERVAL_SECONDS', 1),
],
// 'max_total_results' => env('TYPESENSE_MAX_TOTAL_RESULTS', 1000),
'model-settings' => [
// User::class => [
// 'collection-schema' => [
// 'fields' => [
// [
// 'name' => 'id',
// 'type' => 'string',
// ],
// [
// 'name' => 'name',
// 'type' => 'string',
// ],
// [
// 'name' => 'created_at',
// 'type' => 'int64',
// ],
// ],
// 'default_sorting_field' => 'created_at',
// ],
// 'search-parameters' => [
// 'query_by' => 'name'
// ],
// ],
],
],
];

View File

@@ -34,7 +34,7 @@ window.axios.get('/v1/monthly-views').then(function (response) {
plugins: { plugins: {
title: { title: {
display: true, display: true,
text: 'Views the last 30 days', text: 'Views the last 28 days',
font: { font: {
size: 18 size: 18
} }

View File

@@ -20,3 +20,45 @@ if(localStorage.theme) {
// Default Dark Theme // Default Dark Theme
localStorage.theme = 'dark'; localStorage.theme = 'dark';
} }
// Ability to disable blur effects for slower devices
const LOCAL_STORAGE_KEY = 'blur';
const blurCheckbox = document.querySelector("input[type='checkbox']#toggleBlur");
function setCSSFilter(selector, value) {
document.querySelectorAll(selector).forEach(el => {
el.style.backdropFilter = value
});
}
function applyBlur(enabled) {
if (!enabled) {
setCSSFilter('.backdrop-blur, .backdrop-blur-sm, .backdrop-blur-lg', 'none');
return;
}
setCSSFilter('.backdrop-blur-lg', 'blur(16px)');
setCSSFilter('.backdrop-blur', 'blur(8px)');
setCSSFilter('.backdrop-blur-sm', 'blur(4px)');
}
function initBlurToggle() {
const storedValue = localStorage.getItem(LOCAL_STORAGE_KEY);
const enabled = storedValue === null ? true : storedValue === 'true';
// initialize UI and DOM
applyBlur(enabled);
if (blurCheckbox) blurCheckbox.checked = enabled;
// add event listener
if (blurCheckbox) {
blurCheckbox.addEventListener('click', (e) => {
console.log("Received Event");
const isEnabled = e.target.checked;
applyBlur(isEnabled);
localStorage.setItem(LOCAL_STORAGE_KEY, isEnabled ? 'true' : 'false');
});
}
}
initBlurToggle();

View File

@@ -0,0 +1,5 @@
@extends('admin.layout')
@section('content')
@livewire('admin-comment-search')
@endsection

View File

@@ -19,6 +19,12 @@
<span class="ms-3">Users</span> <span class="ms-3">Users</span>
</a> </a>
</li> </li>
<li>
<a href="{{ route('admin.comments.index') }}" class="flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-pink-700/40 group @if(Route::is('admin.comments.index')) bg-pink-700/40 @endif">
<i class="fa-solid fa-comment"></i>
<span class="ms-3">Comments</span>
</a>
</li>
<li> <li>
<a href="{{ route('admin.contact.index') }}" class="flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-pink-700/40 group @if(Route::is('admin.contact.index')) bg-pink-700/40 @endif"> <a href="{{ route('admin.contact.index') }}" class="flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-pink-700/40 group @if(Route::is('admin.contact.index')) bg-pink-700/40 @endif">
<i class="fa-solid fa-message"></i> <i class="fa-solid fa-message"></i>

View File

@@ -24,7 +24,6 @@
@include('home.partials.random') @include('home.partials.random')
</div> </div>
<!-- Comments --> <!-- Comments -->
<div class="mx-auto pt-6 sm:px-6 lg:px-8 space-y-6 max-w-[100%] xl:max-w-[95%] 2xl:max-w-[85%] pb-2"> <div class="mx-auto pt-6 sm:px-6 lg:px-8 space-y-6 max-w-[100%] xl:max-w-[95%] 2xl:max-w-[85%] pb-2">
@include('home.partials.comments') @include('home.partials.comments')

View File

@@ -59,16 +59,20 @@
</div> </div>
</div> </div>
</x-dropdown-link> </x-dropdown-link>
{{-- Expiremental --}}
<x-dropdown-link>
@include('partials.blurswitcher')
</x-dropdown-link>
</x-slot> </x-slot>
</x-dropdown> </x-dropdown>
</div> </div>
<div class="flex items-center invisible sm:visible"> <div class="items-center hidden md:flex">
@livewire('nav-live-search') @livewire('nav-live-search')
<div class="hidden lg:block pl-4"> <div class="hidden lg:block pl-4">
<div class="flex flex-col items-center bg-gray-50/20 dark:bg-neutral-900/40 rounded-md"> <div class="flex flex-col items-center bg-gray-50/20 dark:bg-neutral-900/40 rounded-md">
@php $random = App\Models\Episode::inRandomOrder()->limit(1)->pluck('slug')->first(); @endphp <a href="{{ route('hentai.random') }}"
<a href="{{ route('hentai.index', ['title' => $random]) }}"
class="cursor-pointer px-4 py-2 text-sm font-medium dark:hover:text-white text-gray-500 dark:text-white/90 focus:outline-none flex flex-col items-center md:flex-row"> class="cursor-pointer px-4 py-2 text-sm font-medium dark:hover:text-white text-gray-500 dark:text-white/90 focus:outline-none flex flex-col items-center md:flex-row">
<i class="fa-solid fa-shuffle"></i> <i class="fa-solid fa-shuffle"></i>
<p class="md:pl-1 pl-0">Random</p> <p class="md:pl-1 pl-0">Random</p>

View File

@@ -0,0 +1,57 @@
<div>
<div class="relative pt-5 text-gray-900 dark:text-white xl:max-w-[95%] 2xl:max-w-[90%]" wire:keydown.right.window="nextPage" wire:keydown.left.window="previousPage">
<div class="flex items-center justify-center">
<div class="relative overflow-x-auto rounded-lg w-3/6">
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-white">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-pink-700 dark:text-neutral-200 ">
<tr>
<th scope="col" class="px-6 py-3 text-center">
User
<input
wire:model.live.debounce.600ms="userSearch"
type="search"
id="live-search"
class="w-32 h-7 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-rose-800 focus:border-rose-900 dark:bg-neutral-900 dark:border-neutral-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-rose-800 dark:focus:border-rose-900"
placeholder="Search..."
>
</th>
<th scope="col" class="px-6 py-3">
Comment
<input
wire:model.live.debounce.600ms="search"
type="search"
id="live-search"
class="ml-2 w-32 h-7 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-rose-800 focus:border-rose-900 dark:bg-neutral-900 dark:border-neutral-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-rose-800 dark:focus:border-rose-900"
placeholder="Search..."
>
</th>
<th scope="col" class="px-6 py-3">
Actions
</th>
</tr>
</thead>
<tbody>
@foreach($comments as $comment)
<tr wire:key="comment-{{ $comment->id }}" class="bg-white border-t dark:bg-neutral-800 dark:border-pink-700">
<td class="px-6 py-4">
<a href="{{ route('user.index', ['username' => $comment->username]) }}">{{ $comment->username }}</a>
</td>
<th scope="row" class="px-6 py-4 font-medium text-gray-900 dark:text-white max-w-lg">
{{ $comment->comment }}
</th>
<td class="px-6 py-4">
<a href="{{ route('comments.destroy', $comment->id) }}" onclick="event.preventDefault();document.getElementById('comment-delete-form-{{ $comment->id }}').submit();" class="inline-flex items-center px-4 py-2 bg-red-600 border border-transparent rounded-md font-semibold text-xs text-white uppercase tracking-widest hover:bg-red-500 active:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 dark:focus:ring-offset-gray-800 transition ease-in-out duration-150 mt-2">@lang('comments::comments.delete')</a>
<form id="comment-delete-form-{{ $comment->id }}" action="{{ route('comments.destroy', $comment->id) }}" method="POST" style="display: none;">
@method('DELETE')
@csrf
</form>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
</div>
{{ $comments->links('pagination::tailwind') }}
</div>
</div>

View File

@@ -74,15 +74,18 @@
<td class="px-6 py-4"> <td class="px-6 py-4">
{{ $user->updated_at->format('Y-m-d') }} {{ $user->updated_at->format('Y-m-d') }}
</td> </td>
<td class="px-6 py-4"> <td class="px-6 py-4 flex flex-col gap-1">
<form method="POST" action="{{ route('admin.user.update') }}"> <form method="POST" action="{{ route('admin.user.update') }}">
@csrf @csrf
<input type="hidden" value="{{ $user->id }}" name="id"> <input type="hidden" value="{{ $user->id }}" name="id">
<input type="hidden" value="{{ $user->is_banned ? 'unban' : 'ban' }}" name="action"> <input type="hidden" value="{{ $user->is_banned ? 'unban' : 'ban' }}" name="action">
<button type="submit" class="inline-block rounded bg-rose-600 pl-[4px] pr-[4px] p-[1px] text-xs font-medium uppercase leading-normal text-white transition duration-150 ease-in-out hover:bg-rose-700 focus:bg-rose-600"> <button type="submit" class="inline-block w-full rounded bg-rose-600 pl-[4px] pr-[4px] p-[1px] text-xs font-medium uppercase leading-normal text-white transition duration-150 ease-in-out hover:bg-rose-700 focus:bg-rose-600">
{{ $user->is_banned ? 'Unban' : 'Ban' }} {{ $user->is_banned ? 'Unban' : 'Ban' }}
</button> </button>
</form> </form>
<button wire:click="deleteUserComments('{{ $user->id }}')" class="inline-block w-full rounded bg-red-600 pl-[4px] pr-[4px] p-[1px] text-xs font-medium uppercase leading-normal text-white transition duration-150 ease-in-out hover:bg-rose-700 focus:bg-rose-600">
Delete comments
</button>
</td> </td>
</tr> </tr>
@endforeach @endforeach

View File

@@ -1,8 +1,8 @@
<div class="py-3"> <div class="py-3 relative">
<div class="mx-auto sm:px-6 lg:px-8 space-y-6 max-w-[100%] xl:max-w-[80%] 2xl:max-w-[50%]"> <div class="mx-auto sm:px-6 lg:px-8 space-y-6 max-w-[100%] lg:max-w-[90%] xl:max-w-[80%] 2xl:max-w-[60%] relative z-10">
<!-- Search Filter --> <!-- Search Filter -->
<div class="p-4 sm:p-8 bg-white/30 dark:bg-neutral-950/40 shadow sm:rounded-lg backdrop-blur"> <div class="p-4 sm:p-8 bg-white/30 dark:bg-neutral-950/40 shadow sm:rounded-lg backdrop-blur relative z-100">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> <div class="grid grid-cols-1 sm:grid-cols-3 gap-4 ">
<!-- Title --> <!-- Title -->
<div> <div>
@@ -36,6 +36,44 @@
</div> </div>
</div> </div>
<div x-data="{ open: @entangle('isOpen') }" x-on:click.away="open = false"
class="relative right-2 left-0 sm:left-2 transition-all">
<div class="absolute inset-y-0 left-2 flex items-center pl-3 pointer-events-none">
<i class="fa-solid fa-filter text-gray-500 dark:text-gray-400"></i>
</div>
<button type="button"
class="inline-flex w-full p-4 pl-10 text-sm text-gray-900 border border-gray-300 rounded-lg bg-gray-50 focus:ring-rose-800 focus:border-rose-900 dark:bg-neutral-900 dark:border-neutral-600 dark:placeholder-gray-400 dark:text-white dark:focus:ring-rose-800 dark:focus:border-rose-900"
wire:click="toggleDropdown">
Select Type
<svg class="absolute -mr-1 h-5 w-5 text-gray-500 right-4 top-4"
xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20" fill="currentColor"
aria-hidden="true">
<path fill-rule="evenodd"
d="M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z"
clip-rule="evenodd" />
</svg>
</button>
<!-- Dropdown Menu -->
@if ($isOpen)
<div x-show="open" x-transition @click.away="open = false"
class="absolute mt-2 w-full rounded-lg bg-white dark:bg-neutral-900 border-[1px] border-gray-300 dark:border-neutral-600 shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none z-[110]">
<div class="py-1">
@foreach ($options as $label => $selected)
<label
class="flex items-center px-4 py-2 text-sm text-gray-700 cursor-pointer hover:bg-gray-100 dark:hover:bg-neutral-800">
<input type="checkbox" wire:click="toggleOption('{{ $label }}')"
class="h-4 w-4 text-rose-600 bg-gray-100 border-gray-300 rounded focus:ring-rose-500 dark:focus:ring-rose-600 dark:ring-offset-gray-800 focus:ring-2 dark:bg-gray-700 dark:border-gray-600"
{{ $selected ? 'checked' : '' }}>
<span class="ml-2 text-gray-900 dark:text-white">{{ $label }}</span>
</label>
@endforeach
</div>
</div>
@endif
</div>
<!-- Ordering --> <!-- Ordering -->
<div> <div>
<div class="relative right-2 left-0 sm:left-2 transition-all"> <div class="relative right-2 left-0 sm:left-2 transition-all">
@@ -56,7 +94,8 @@
</div> </div>
</div> </div>
</div> </div>
<div class="relative pt-5 mx-auto sm:px-6 lg:px-8 space-y-6 text-gray-900 dark:text-white max-w-[100%] xl:max-w-[80%] 2xl:max-w-[50%]"> <div
class="relative pt-5 mx-auto sm:px-6 lg:px-8 space-y-6 text-gray-900 dark:text-white max-w-[100%] lg:max-w-[90%] xl:max-w-[80%] 2xl:max-w-[60%]">
<div class="flex flex-col"> <div class="flex flex-col">
<div class="overflow-x-auto sm:-mx-6 lg:-mx-8"> <div class="overflow-x-auto sm:-mx-6 lg:-mx-8">
<div class="inline-block min-w-full py-2 sm:px-6 lg:px-8"> <div class="inline-block min-w-full py-2 sm:px-6 lg:px-8">
@@ -68,9 +107,10 @@
<div <div
class="flex bg-white/30 dark:bg-neutral-950/40 backdrop-blur font-medium dark:border-neutral-500 border-b rounded-tl-lg rounded-tr-lg"> class="flex bg-white/30 dark:bg-neutral-950/40 backdrop-blur font-medium dark:border-neutral-500 border-b rounded-tl-lg rounded-tr-lg">
<div class="flex-1 px-6 py-4 hidden sm:block">Title</div> <div class="flex-1 px-6 py-4 hidden sm:block">Title</div>
<div class="w-28 px-6 py-4 hidden sm:block">Size</div> <div class="w-20 px-6 py-4 hidden text-center sm:block">Type</div>
<div class="w-32 px-6 py-4 hidden sm:block">Date</div> <div class="w-28 px-6 py-4 hidden text-center sm:block">Size</div>
<div class="w-32 px-6 py-4">Download</div> <div class="w-32 px-6 py-4 hidden text-center sm:block">Date</div>
<div class="w-32 px-6 py-4 text-center">Download</div>
</div> </div>
@php @php
@@ -91,8 +131,58 @@
{{ $title }} {{ $title }}
</div> </div>
<!-- Type -->
<div class="w-20 whitespace-nowrap flex justify-center">
<span class="block sm:hidden pr-2">
Type:
</span>
<div class="flex justify-center">
@if ($download->type === 'FHD' || $download->type === 'FHDi')
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
class="text-sky-500" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"
class="icon icon-tabler icons-tabler-outline icon-tabler-badge-hd">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M3 5m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" />
<path d="M14 9v6h1a2 2 0 0 0 2 -2v-2a2 2 0 0 0 -2 -2h-1z" />
<path d="M7 15v-6" />
<path d="M10 15v-6" />
<path d="M7 12h3" />
</svg>
@else
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"
class="text-rose-600" viewBox="0 0 24 24" fill="none"
stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round"
class="icon icon-tabler icons-tabler-outline icon-tabler-badge-4k">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M3 5m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v10a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" />
<path d="M7 9v2a1 1 0 0 0 1 1h1" />
<path d="M10 9v6" />
<path d="M14 9v6" />
<path d="M17 9l-2 3l2 3" />
<path d="M15 12h-1" />
</svg>
@endif
@if ($download->type === 'FHDi' || $download->type === 'UHDi')
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" class="text-yellow-600"
viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round">
<path
d="M9.225 18.412a1.595 1.595 0 0 1 -1.225 .588c-.468 0 -.914 -.214 -1.225 -.588l-4.361 -5.248a1.844 1.844 0 0 1 0 -2.328l4.361 -5.248a1.595 1.595 0 0 1 1.225 -.588c.468 0 .914 .214 1.225 .588l4.361 5.248a1.844 1.844 0 0 1 0 2.328l-4.361 5.248z" />
<path d="M17 5l4.586 5.836a1.844 1.844 0 0 1 0 2.328l-4.586 5.836" />
<path d="M13 5l4.586 5.836a1.844 1.844 0 0 1 0 2.328l-4.586 5.836" />
</svg>
@endif
</div>
</div>
<!-- Size --> <!-- Size -->
<div class="w-28 whitespace-nowrap flex"> <div class="w-28 whitespace-nowrap flex justify-center">
<span class="block sm:hidden"> <span class="block sm:hidden">
Size: Size:
</span> </span>
@@ -100,7 +190,7 @@
</div> </div>
<!-- Date --> <!-- Date -->
<div class="w-32 whitespace-nowrap flex"> <div class="w-32 whitespace-nowrap flex justify-center">
<span class="block sm:hidden"> <span class="block sm:hidden">
Date: Date:
</span> </span>
@@ -108,18 +198,29 @@
</div> </div>
<!-- Download --> <!-- Download -->
<div class="w-32 whitespace-nowrap"> <div class="w-32 whitespace-nowrap flex justify-center">
@php @php
if (in_array($download->type, ['FHD', 'FHDi'])) { if (in_array($download->type, ['FHD', 'FHDi'])) {
$downloadURL = $dldomains[array_rand($dldomains)].'/'.$download->url; $downloadURL =
$dldomains[array_rand($dldomains)] . '/' . $download->url;
} else { } else {
$now = \Illuminate\Support\Carbon::now(); $now = \Illuminate\Support\Carbon::now();
$expire = \Illuminate\Support\Facades\Crypt::encryptString($now->addHours(6)); $expire = \Illuminate\Support\Facades\Crypt::encryptString(
$file = \Illuminate\Support\Facades\Crypt::encryptString('hentai/'.$download->url); $now->addHours(6),
$downloadURL = $dlpdomains[array_rand($dlpdomains)].'/download/'.$file.'/'.$expire; );
$file = \Illuminate\Support\Facades\Crypt::encryptString(
'hentai/' . $download->url,
);
$downloadURL =
$dlpdomains[array_rand($dlpdomains)] .
'/download/' .
$file .
'/' .
$expire;
} }
@endphp @endphp
<a href="{{ $downloadURL }}" wire:click="clicked({{ $download->id }})" download> <a href="{{ $downloadURL }}" wire:click="clicked({{ $download->id }})"
download>
<button id="dl-{{ $download->id }}" <button id="dl-{{ $download->id }}"
class="group rounded-md bg-transparent border-[1px] border-white/20 shadow dark:text-white text-blac cursor-pointer flex justify-between items-center overflow-hidden transition-all w-[110px] h-[32px] mt-1 mb-1"> class="group rounded-md bg-transparent border-[1px] border-white/20 shadow dark:text-white text-blac cursor-pointer flex justify-between items-center overflow-hidden transition-all w-[110px] h-[32px] mt-1 mb-1">
<div <div

View File

@@ -1,63 +1,111 @@
<div class="flex items-center"> <div class="flex items-center relative">
<form method="POST" action="{{ route('hentai.searchredirect') }}"> <form method="POST" action="{{ route('hentai.searchredirect') }}" class="w-full">
@csrf @csrf
<label for="live-search" class="mb-2 text-sm font-medium text-gray-900 sr-only dark:text-white">Search</label> <div class="relative group">
<div class="absolute right-2 left-2 sm:relative sm:min-w-[200px] md:min-w-[300px] lg:min-w-[400px] xl:min-w-[500px] transition-all"> <label for="live-search" class="sr-only">Search</label>
<div class="flex absolute inset-y-0 left-0 items-center pl-3 pointer-events-none"> <div class="relative w-full sm:min-w-[200px] md:min-w-[300px] lg:min-w-[400px] xl:min-w-[500px]">
<svg class="w-4 h-4 text-gray-500 dark:text-gray-400" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20"> {{-- Search Icon --}}
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/> <div class="pointer-events-none absolute inset-y-0 left-0 pl-3 flex items-center">
</svg> <svg class="w-4 h-4 text-gray-400 dark:text-gray-300" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 20 20">
</div> <path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m19 19-4-4m0-7A7 7 0 1 1 1 8a7 7 0 0 1 14 0Z"/>
<input wire:model.live.debounce.600ms="navSearch" type="search" id="live-search" name="live-search" class="block p-4 pl-10 w-full text-sm text-gray-900 rounded-lg border border-gray-300/50 bg-gray-50/20 focus:ring-rose-800 focus:border-rose-900 dark:bg-neutral-900/40 dark:border-neutral-600/50 dark:placeholder-gray-400 dark:text-white dark:focus:ring-rose-800 dark:focus:border-rose-900" placeholder="@if(request()->path() !== 'search'){{ __('search.search-hentai') }}@endif" required @if(request()->path() == 'search') disabled @endif> </svg>
<button type="submit" class="absolute right-2.5 bottom-2.5 px-4 py-2 text-sm font-medium text-white bg-rose-700 rounded-lg hover:bg-rose-800 disabled:bg-gray-300 disabled:hover:bg-gray-300 disabled:dark:bg-gray-500 disabled:dark:hover:bg-gray-500 focus:ring-4 focus:outline-none focus:ring-rose-300 dark:bg-rose-600 dark:hover:bg-rose-700 dark:focus:ring-rose-800" @if(request()->path() == 'search') disabled @endif>{{ __('search.search') }}</button> </div>
</div>
</form>
@if((! $hide) && request()->path() != 'search' && request()->path() != 'download-search') {{-- Search Input --}}
<!-- BG Blur and BG Size --> <input
<div class="absolute left-0 sm:top-[65px] w-[100%] h-[calc(100vh-60px)] z-40 text-gray-900 dark:text-white bg-neutral-100/80 dark:bg-neutral-900/80"> wire:model.live.debounce.600ms="navSearch"
<div class="flex justify-center items-center"> type="search"
<!-- Padding for Grid --> id="live-search"
<div class="flex justify-center w-5/6"> name="live-search"
<div class="grid grid-cols-1 gap-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5"> class="block w-full pl-10 pr-28 py-3 text-sm rounded-2xl border border-gray-200 bg-white/80 dark:bg-neutral-900/50 dark:border-neutral-700 placeholder-gray-400 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-rose-600 focus:border-rose-700 transition"
@foreach($episodes as $episode) placeholder="@if(request()->path() !== 'search'){{ __('search.search-hentai') }}@endif"
<div class="relative p-1 mb-8 w-full transition duration-300 ease-in-out md:p-2 hover:-translate-y-1 hover:scale-110"> required
<a class="hover:text-blue-600" href="{{ route('hentai.index', ['title' => $episode->slug ]) }}"> @if(request()->path() == 'search') disabled @endif
<div class="absolute w-[95%] top-[38%] text-center z-10"> aria-autocomplete="list"
<svg aria-hidden="true" class="inline mr-2 w-8 h-8 text-gray-200 animate-spin dark:text-gray-600 fill-pink-600" viewBox="0 0 100 101" fill="none" xmlns="http://www.w3.org/2000/svg"> aria-label="Search"
<path d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z" fill="currentColor"/> >
<path d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z" fill="currentFill"/>
{{-- Submit button to redirect to advanced search --}}
<button
type="submit"
class="absolute right-1 top-1/2 -translate-y-1/2 px-4 py-2 text-sm font-medium rounded-xl bg-rose-700 text-white hover:bg-rose-800 focus:outline-none focus:ring-2 focus:ring-rose-300 disabled:bg-gray-300 disabled:hover:bg-gray-300 disabled:dark:bg-gray-500 disabled:dark:hover:bg-gray-500"
@if(request()->path() == 'search') disabled @endif
>{{ __('search.search') }}</button>
</div>
@if((! $hide) && request()->path() != 'search' && request()->path() != 'download-search')
<div
class="pointer-events-auto absolute left-0 right-0 mt-3 z-50 flex justify-center"
aria-live="polite"
>
<div
class="max-h-[70vh] overflow-auto rounded-2xl bg-white dark:bg-neutral-900 border border-gray-200 dark:border-neutral-700 shadow-lg transition-all transform hidden group-focus-within:block group-focus-within:translate-y-0">
<div class="flex items-center justify-between p-3 border-b border-gray-100 dark:border-neutral-800">
<div class="text-sm text-gray-700 dark:text-gray-200 font-medium">
@if($episodes->count())
{{ __('Search result for ') }} {{ $query ?: $navSearch }}
@else
{{ __('No results') }}
@endif
</div>
{{-- Loading indicator using Livewire --}}
<div class="flex items-center gap-2">
<div wire:loading.class.remove="hidden" class="hidden">
<svg class="w-5 h-5 animate-spin text-rose-600" viewBox="0 0 24 24" fill="none">
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-opacity="0.2" stroke-width="4"></circle>
<path d="M22 12a10 10 0 0 1-10 10" stroke="currentColor" stroke-width="4" stroke-linecap="round"></path>
</svg> </svg>
</div> </div>
<img </div>
alt="{{ $episode->title }} - {{ $episode->episode }}"
loading="lazy"
width="500"
class="block object-cover object-center relative z-20 rounded-lg aspect-video"
src="{{ $episode->gallery->first()->thumbnail_url }}">
</img>
<p class="absolute right-4 top-4 bg-white/80 dark:bg-neutral-700/80 !text-gray-900 dark:!text-white rounded-bl-lg rounded-lg p-1 pr-2 pl-2 font-semibold text-sm">{{ $episode->getResolution() }}</p>
<p class="absolute left-4 bottom-4 bg-white/80 dark:bg-neutral-700/80 !text-gray-900 dark:!text-white rounded-bl-lg rounded-lg p-1 pr-2 pl-2 font-semibold text-sm"><i class="fa-regular fa-eye"></i> {{ $episode->view_count }} <i class="fa-regular fa-heart"></i> {{ count($episode->likes) }}</p>
<p class="absolute w-[95%] text-center text-sm">{{ $episode->title }} - {{ $episode->episode }}</p>
</a>
</div> </div>
@endforeach
<div class="relative p-1 mb-8 w-full transition duration-300 ease-in-out md:p-2 hover:-translate-y-1 hover:scale-110"> {{-- content area: responsive grid --}}
<a class="hover:text-blue-600" href="{{ route('hentai.search', ['s' => $query]) }}"> <div class="p-4">
<img @if($episodes->count())
alt="gallery" <div class="grid grid-cols-1 gap-4 md:grid-cols-1 lg:grid-cols-2">
loading="lazy" @foreach($episodes as $episode)
width="500" <a href="{{ route('hentai.index', ['title' => $episode->slug ]) }}" class="group block rounded-xl overflow-hidden bg-neutral-50 dark:bg-neutral-950 border border-transparent hover:border-gray-200 dark:hover:border-neutral-700 shadow-sm hover:shadow-md transition">
class="block object-cover object-center rounded-lg aspect-video" <div class="relative aspect-video">
src="{{ $randomimage->thumbnail_url }}"> <img
</img> alt="{{ $episode->title }} - {{ $episode->episode }}"
<p class="absolute left-2 top-2 w-[95%] h-[91.5%] bg-white/10 dark:bg-neutral-700/10 !text-gray-900 dark:!text-white rounded-bl-lg rounded-lg font-semibold p-4 pr-8 pl-8 text-center"></p> loading="lazy"
<p class="absolute left-[20%] top-[35%] bg-white/80 dark:bg-neutral-700/80 !text-gray-900 dark:!text-white rounded-bl-lg rounded-lg font-semibold p-4 pr-8 pl-8 text-center">Advanced Search...</p> class="object-cover w-full h-full"
</a> src="{{ $episode->gallery->first()->thumbnail_url }}"
>
<span class="absolute right-0 top-0 bg-white/90 dark:bg-neutral-800/80 dark:text-white text-xs font-semibold rounded-tr rounded-bl-xl px-2 py-1">{{ $episode->getResolution() }}</span>
<div class="absolute left-0 bottom-0 bg-white/90 dark:bg-neutral-800/80 dark:text-white text-xs rounded-tr-xl px-2 py-1 font-medium">
<i class="fa-regular fa-eye mr-1"></i> {{ $episode->viewCountFormatted() }}
<i class="fa-regular fa-heart ml-2"></i> {{ $episode->likeCount() }}
</div>
</div>
<div class="p-3">
<h3 class="text-sm font-semibold truncate text-gray-900 dark:text-white">{{ $episode->title }} - {{ $episode->episode }}</h3>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1 truncate"> {{ \Illuminate\Support\Str::limit($episode->description ?? '', 80) }}</p>
</div>
</a>
@endforeach
{{-- Advanced Search card --}}
<a href="{{ route('hentai.search', ['search' => $query]) }}" class="flex items-center justify-center rounded-xl border border-dashed border-gray-200 dark:border-neutral-700 p-6 hover:bg-gray-50 dark:hover:bg-neutral-900 transition">
<div class="text-center">
<div class="text-2xl font-bold text-rose-600 mb-1">🔎</div>
<div class="font-semibold text-sm dark:text-white">Advanced Search</div>
<div class="text-xs text-gray-500 dark:text-gray-400 mt-1">View more results</div>
</div>
</a>
</div>
@else
{{-- Empty state --}}
<div class="py-12 text-center text-sm text-gray-600 dark:text-gray-300">
<div class="mb-3">No results found for {{ $query ?: $navSearch }}</div>
<a href="{{ route('hentai.search', ['search' => $navSearch ?: $query]) }}" class="inline-block px-4 py-2 rounded-lg bg-rose-700 text-white text-sm hover:bg-rose-800">Try advanced search</a>
</div>
@endif
</div> </div>
</div> </div>
</div> </div>
@endif
</div> </div>
</div> </form>
@endif
</div> </div>

View File

@@ -0,0 +1,36 @@
<div class="grid grid-cols-2">
<p class="cursor-default">{{ __('Blur effects') }}</p>
<div class="flex items-center">
<div class="absolute right-6">
<label for="toggleBlur" class="flex items-center cursor-pointer">
<!-- toggle -->
<div class="relative">
<!-- input -->
<input id="toggleBlur" type="checkbox" class="sr-only" checked />
<!-- line -->
<div class="w-10 h-4 bg-rose-600 dark:bg-gray-400 rounded-full shadow-inner">
</div>
<!-- dot -->
<div
class="dot absolute w-6 h-6 bg-white dark:bg-neutral-950 rounded-full shadow -left-1 -top-1 transition">
<div class="items-center ml-[4px] w-6 h-6 font-medium flex">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"
stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-blur">
<path stroke="none" d="M0 0h24v24H0z" fill="none" />
<path
d="M12 21a9.01 9.01 0 0 0 2.32 -.302a9 9 0 0 0 1.74 -16.733a9 9 0 1 0 -4.06 17.035z" />
<path d="M12 3v17" />
<path d="M12 12h9" />
<path d="M12 9h8" />
<path d="M12 6h6" />
<path d="M12 18h6" />
<path d="M12 15h8" />
</svg>
</div>
</div>
</div>
</label>
</div>
</div>
</div>

View File

@@ -22,6 +22,7 @@ use Illuminate\Support\Facades\Route;
Route::get('/', [HomeController::class, 'index'])->name('home.index'); Route::get('/', [HomeController::class, 'index'])->name('home.index');
Route::get('/stats', [HomeController::class, 'stats'])->name('home.stats'); Route::get('/stats', [HomeController::class, 'stats'])->name('home.stats');
Route::get('/banned', [HomeController::class, 'banned'])->name('home.banned'); Route::get('/banned', [HomeController::class, 'banned'])->name('home.banned');
Route::get('/random', [HomeController::class, 'random'])->name('hentai.random');
// API Endpoint // API Endpoint
Route::get('/v1/hentai-list', [HentaiApiController::class, 'index'])->name('api.hentai.index'); Route::get('/v1/hentai-list', [HentaiApiController::class, 'index'])->name('api.hentai.index');
@@ -101,6 +102,9 @@ Route::group(['middleware' => ['auth', 'auth.admin']], function () {
Route::get('/admin/users', [App\Http\Controllers\Admin\UserController::class, 'index'])->name('admin.user.index'); Route::get('/admin/users', [App\Http\Controllers\Admin\UserController::class, 'index'])->name('admin.user.index');
Route::post('/admin/users', [App\Http\Controllers\Admin\UserController::class, 'update'])->name('admin.user.update'); Route::post('/admin/users', [App\Http\Controllers\Admin\UserController::class, 'update'])->name('admin.user.update');
// Comments
Route::get('/admin/comments', [App\Http\Controllers\Admin\CommentsController::class, 'index'])->name('admin.comments.index');
// Contact page overview // Contact page overview
Route::get('/admin/contact', [App\Http\Controllers\Admin\ContactController::class, 'index'])->name('admin.contact.index'); Route::get('/admin/contact', [App\Http\Controllers\Admin\ContactController::class, 'index'])->name('admin.contact.index');
Route::delete('/admin/contact/{contact_id}', [App\Http\Controllers\Admin\ContactController::class, 'delete'])->name('admin.contact.delete'); Route::delete('/admin/contact/{contact_id}', [App\Http\Controllers\Admin\ContactController::class, 'delete'])->name('admin.contact.delete');