Compare commits
8 Commits
a6fe34a0d1
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
| f8022b3f18 | |||
| 57d1ec34c3 | |||
| 81639aaabf | |||
| 5dc1bff60c | |||
| 2f3f0edc30 | |||
| a71b2976af | |||
| 2c016274ab | |||
| 5ba0a55316 |
@@ -16,7 +16,7 @@ class GenerateSitemap extends Command
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'sitemap:generate';
|
||||
protected $signature = 'app:generate-sitemap';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
|
||||
@@ -8,6 +8,7 @@ use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Crypt;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Contracts\Encryption\DecryptException;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class SyncSubscriptionKeys extends Command
|
||||
{
|
||||
@@ -83,8 +84,11 @@ class SyncSubscriptionKeys extends Command
|
||||
->whereNotIn('subscription_key', $activeKeys)
|
||||
->chunk(100, function ($users) {
|
||||
foreach($users as $user) {
|
||||
if ($user->hasRole(UserRole::SUPPORTER)) {
|
||||
Log::info("Removed Supporter Role from {$user->name}");
|
||||
$user->removeRole(UserRole::SUPPORTER);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -95,8 +99,11 @@ class SyncSubscriptionKeys extends Command
|
||||
->whereIn('subscription_key', $activeKeys)
|
||||
->chunk(100, function ($users) {
|
||||
foreach($users as $user) {
|
||||
if (!$user->hasRole(UserRole::SUPPORTER)) {
|
||||
Log::info("Added Supporter Role for {$user->name}");
|
||||
$user->addRole(UserRole::SUPPORTER);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Controllers\Admin;
|
||||
|
||||
use App\Enums\UserRole;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Jobs\DiscordReleaseNotification;
|
||||
use App\Models\Episode;
|
||||
@@ -63,6 +64,17 @@ class EpisodeController extends Controller
|
||||
public function update(Request $request): RedirectResponse
|
||||
{
|
||||
$episode = Episode::with('hentai')->where('id', $request->input('episode_id'))->firstOrFail();
|
||||
|
||||
if ($request->user()->hasRole(UserRole::MODERATOR)) {
|
||||
$this->episodeService->updateEpisodeModerator($request, $episode->id);
|
||||
|
||||
cache()->flush();
|
||||
|
||||
return to_route('hentai.index', [
|
||||
'title' => $episode->slug,
|
||||
]);
|
||||
}
|
||||
|
||||
$studio = $this->episodeService->getOrCreateStudio(json_decode($request->input('studio'))[0]->value);
|
||||
|
||||
$oldinterpolated = $episode->interpolated;
|
||||
|
||||
@@ -23,11 +23,21 @@ class DownloadButton extends Component
|
||||
|
||||
public $fileExtension = 'HEVC';
|
||||
|
||||
public $version = '';
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (str_contains($this->downloadUrl, 'AV1')) {
|
||||
$this->fileExtension = 'AV1';
|
||||
}
|
||||
|
||||
if (str_contains($this->downloadUrl, 'v2')) {
|
||||
$this->version = 'v2';
|
||||
}
|
||||
|
||||
if (str_contains($this->downloadUrl, 'v3')) {
|
||||
$this->version = 'v3';
|
||||
}
|
||||
}
|
||||
|
||||
public function clicked($downloadId)
|
||||
|
||||
@@ -5,6 +5,7 @@ namespace App\Services;
|
||||
use App\Models\Episode;
|
||||
use App\Models\Hentai;
|
||||
use App\Models\Studios;
|
||||
use App\Models\ModLog;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
@@ -62,6 +63,68 @@ class EpisodeService
|
||||
return $episode;
|
||||
}
|
||||
|
||||
private function applyTags(Request $request, Episode $episode): void
|
||||
{
|
||||
$tags = json_decode($request->input('tags'));
|
||||
$newtags = [];
|
||||
foreach ($tags as $t) {
|
||||
$newtags[] = $t->value;
|
||||
}
|
||||
|
||||
$newTagsTemp = $newtags;
|
||||
$oldTagsTemp = $episode->tagNames();
|
||||
|
||||
sort($newTagsTemp);
|
||||
sort($oldTagsTemp);
|
||||
|
||||
if ($newTagsTemp !== $oldTagsTemp) {
|
||||
ModLog::create([
|
||||
'moderator' => $request->user()->name,
|
||||
'data' => sprintf(
|
||||
'Updated Episode tags from %s to %s',
|
||||
implode(', ', $oldTagsTemp),
|
||||
implode(', ', $newTagsTemp),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
$episode->retag($newtags);
|
||||
}
|
||||
|
||||
private function updateTitle(Request $request, Episode $episode): void
|
||||
{
|
||||
$updates = [];
|
||||
|
||||
if ($episode->title !== $request->input('title')) {
|
||||
$updates['title'] = $request->input('title');
|
||||
$updates['title_search'] = preg_replace(
|
||||
'/[^A-Za-z0-9 ]/',
|
||||
'',
|
||||
$request->input('title')
|
||||
);
|
||||
|
||||
// Log to ModLog
|
||||
ModLog::create([
|
||||
'moderator' => $request->user()->name,
|
||||
'data' => "Updating Hentai Title from {$episode->title} to {$request->input('title')}",
|
||||
]);
|
||||
}
|
||||
|
||||
if ($episode->title_jpn !== $request->input('title_jpn')) {
|
||||
$updates['title_jpn'] = $request->input('title_jpn');
|
||||
|
||||
// Log to ModLog
|
||||
ModLog::create([
|
||||
'moderator' => $request->user()->name,
|
||||
'data' => "Updating Hentai Title from {$episode->title_jpn} to {$request->input('title_jpn')}",
|
||||
]);
|
||||
}
|
||||
|
||||
if (! empty($updates)) {
|
||||
$episode->hentai->episodes()->update($updates);
|
||||
}
|
||||
}
|
||||
|
||||
public function updateEpisode(Request $request, Studios $studio, int $episodeId): Episode
|
||||
{
|
||||
$episode = Episode::where('id', $episodeId)->firstOrFail();
|
||||
@@ -75,17 +138,31 @@ class EpisodeService
|
||||
$episode->dmca_takedown = $request->input('dmca_takedown') == 'true';
|
||||
$episode->save();
|
||||
|
||||
// Tagging
|
||||
$tags = json_decode($request->input('tags'));
|
||||
$newtags = [];
|
||||
foreach ($tags as $t) {
|
||||
$newtags[] = $t->value;
|
||||
}
|
||||
$episode->retag($newtags);
|
||||
$this->applyTags($request, $episode);
|
||||
$this->updateTitle($request, $episode);
|
||||
|
||||
return $episode;
|
||||
}
|
||||
|
||||
public function updateEpisodeModerator(Request $request, int $episodeId): void
|
||||
{
|
||||
$episode = Episode::where('id', $episodeId)->firstOrFail();
|
||||
$oldDescription = $episode->description;
|
||||
$episode->description = $request->input('description');
|
||||
$episode->save();
|
||||
|
||||
if ($episode->description !== $oldDescription) {
|
||||
// Log to ModLog
|
||||
ModLog::create([
|
||||
'moderator' => $request->user()->name,
|
||||
'data' => "Updated Episode description from {$oldDescription} to {$episode->description}",
|
||||
]);
|
||||
}
|
||||
|
||||
$this->applyTags($request, $episode);
|
||||
$this->updateTitle($request, $episode);
|
||||
}
|
||||
|
||||
public function getOrCreateStudio(string $studioName): Studios
|
||||
{
|
||||
return Studios::firstOrCreate(
|
||||
|
||||
Generated
+430
-427
File diff suppressed because it is too large
Load Diff
Generated
+382
-319
File diff suppressed because it is too large
Load Diff
@@ -1,68 +0,0 @@
|
||||
function updateGrid(grid) {
|
||||
// Skip hidden grids
|
||||
if (grid.offsetParent === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const items = [...grid.querySelectorAll('.episode-item')];
|
||||
|
||||
if (!items.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reset visibility first
|
||||
items.forEach(item => {
|
||||
item.style.display = '';
|
||||
});
|
||||
|
||||
// Determine actual column count
|
||||
const firstTop = items[0].offsetTop;
|
||||
|
||||
let columns = 0;
|
||||
|
||||
for (const item of items) {
|
||||
if (item.offsetTop !== firstTop) {
|
||||
break;
|
||||
}
|
||||
|
||||
columns++;
|
||||
}
|
||||
|
||||
const rows = parseInt(grid.dataset.rows || '2', 10);
|
||||
|
||||
const visibleItems = columns * rows;
|
||||
|
||||
items.forEach((item, index) => {
|
||||
item.style.display = index < visibleItems
|
||||
? ''
|
||||
: 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function updateAllEpisodeGrids() {
|
||||
document.querySelectorAll('.episode-grid').forEach(updateGrid);
|
||||
}
|
||||
|
||||
const observer = new IntersectionObserver(entries => {
|
||||
entries.forEach(entry => {
|
||||
if (entry.isIntersecting) {
|
||||
updateGrid(entry.target);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.episode-grid').forEach(grid => {
|
||||
observer.observe(grid);
|
||||
});
|
||||
|
||||
let resizeTimeout;
|
||||
|
||||
window.addEventListener('resize', () => {
|
||||
clearTimeout(resizeTimeout);
|
||||
|
||||
resizeTimeout = setTimeout(() => {
|
||||
updateAllEpisodeGrids();
|
||||
}, 100);
|
||||
});
|
||||
|
||||
window.addEventListener('load', updateAllEpisodeGrids);
|
||||
@@ -1,13 +1,32 @@
|
||||
<div data-te-modal-init class="fixed left-0 top-0 z-[1055] hidden h-full w-full overflow-y-auto overflow-x-hidden outline-none" id="modalEditEpisode" tabindex="-1" aria-labelledby="Upload" aria-modal="true" role="dialog">
|
||||
<div data-te-modal-dialog-ref class="pointer-events-none relative flex min-h-[calc(100%-1rem)] w-auto translate-y-[-50px] items-center opacity-0 transition-all duration-300 ease-in-out min-[576px]:mx-auto min-[576px]:mt-7 min-[576px]:min-h-[calc(100%-3.5rem)] min-[576px]:max-w-[95%] md:min-[576px]:max-w-[90%] lg:min-[576px]:max-w-[80%] xl:min-[576px]:max-w-[70%] 2xl:min-[576px]:max-w-[50%]">
|
||||
<div class="flex relative flex-col w-full text-current bg-clip-padding bg-white rounded-md border-none shadow-lg outline-none pointer-events-auto dark:bg-neutral-800">
|
||||
<div
|
||||
data-te-modal-init
|
||||
id="modalEditEpisode"
|
||||
tabindex="-1"
|
||||
aria-modal="true"
|
||||
role="dialog"
|
||||
class="fixed inset-0 z-[1055] hidden overflow-y-auto bg-black/60 backdrop-blur-sm"
|
||||
>
|
||||
<div data-te-modal-dialog-ref class="flex min-h-screen items-center justify-center p-4">
|
||||
<div class="relative w-full max-w-7xl overflow-hidden rounded-2xl border border-neutral-200 bg-white shadow-2xl dark:border-neutral-700 dark:bg-neutral-900">
|
||||
<x-modal-header :title="__('Edit Episode')"/>
|
||||
|
||||
<!--Modal body-->
|
||||
<div class="relative p-4 pt-0">
|
||||
<form method="POST" action="{{ route('admin.edit') }}" enctype="multipart/form-data">
|
||||
<form method="POST" action="{{ route('admin.episode.edit') }}" enctype="multipart/form-data">
|
||||
@csrf
|
||||
<div class="grid grid-cols-3">
|
||||
<div class="flex flex-col gap-2 p-2">
|
||||
<div>
|
||||
<label class="leading-tight text-gray-800 dark:text-gray-200 w-full" for="title">Title:</label>
|
||||
<x-text-input id="title" value="{{ $episode->title }}" class="block w-full" type="text" name="title" required autofocus/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="leading-tight text-gray-800 dark:text-gray-200 w-full" for="title_jpn">Title JPN:</label>
|
||||
<x-text-input id="title_jpn" value="{{ $episode->title_jpn }}" class="block w-full" type="text" name="title_jpn" required />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-3 p-2">
|
||||
<div class="col-span-2">
|
||||
<!-- Tags -->
|
||||
<div class="row-span-2 p-0">
|
||||
@@ -16,6 +35,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(auth()->user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
|
||||
<div class="grid grid-rows-2">
|
||||
<!-- Studio -->
|
||||
<div class="p-2 pt-0">
|
||||
@@ -47,13 +67,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@if(auth()->user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
|
||||
<!-- Stream URL -->
|
||||
<div class="p-2 pt-0">
|
||||
<label class="w-full leading-tight text-gray-800 dark:text-gray-200" for="baseurl">Stream:</label>
|
||||
<x-text-input id="baseurl" class="block w-full" type="text" name="baseurl" value="{{ $episode->url }}" required />
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<input name="episode_id" id="episode_id" type="hidden" value="{{ $episode->id }}" />
|
||||
|
||||
@@ -62,6 +85,7 @@
|
||||
<textarea rows="4" cols="50" id="description" name="description" class="block mt-1 w-full rounded-md border-gray-300 shadow-sm dark:border-gray-700 dark:bg-neutral-900 dark:text-gray-300 focus:border-rose-500 dark:focus:border-rose-600 focus:ring-rose-500 dark:focus:ring-rose-600" required>{{ $episode->description }}</textarea>
|
||||
</div>
|
||||
|
||||
@if(auth()->user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
|
||||
<!-- Episodes -->
|
||||
<div class="grid grid-cols-2">
|
||||
<!-- Cover -->
|
||||
@@ -95,8 +119,10 @@
|
||||
<label class="w-full leading-tight text-gray-800 dark:text-gray-200" for="downloadUHDi1">Download 4k Interpolated:</label>
|
||||
<x-text-input id="downloadUHDi1" class="block w-full" type="text" name="downloadUHDi1" value="{{ $episode->getDownloadByType('UHDi')->url ?? '' }}" />
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="flex flex-wrap flex-shrink-0 justify-end items-center p-4 rounded-b-md">
|
||||
<div class="sticky bottom-0 flex items-center justify-end gap-3 border-t border-neutral-200 bg-white/90 px-6 py-4 backdrop-blur dark:border-neutral-700 dark:bg-neutral-900/90">
|
||||
@if(auth()->user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
|
||||
<div class="inline-block mr-2">
|
||||
<input class="w-4 h-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"
|
||||
type="checkbox" value="true" id="v2" name="v2" />
|
||||
@@ -111,10 +137,17 @@
|
||||
DMCA Takedown
|
||||
</label>
|
||||
</div>
|
||||
<button type="button" class="inline-block px-6 pt-2.5 pb-2 text-xs font-medium leading-normal uppercase rounded transition duration-150 ease-in-out bg-primary-100 text-primary-700 hover:bg-primary-accent-100 focus:bg-primary-accent-100 focus:outline-none focus:ring-0 active:bg-primary-accent-200" data-te-modal-dismiss data-te-ripple-init data-te-ripple-color="light">
|
||||
@endif
|
||||
<button
|
||||
type="button"
|
||||
data-te-modal-dismiss
|
||||
class="rounded-xl border border-neutral-300 px-5 py-2.5 text-sm font-medium text-neutral-700 transition hover:bg-neutral-100 dark:border-neutral-600 dark:text-neutral-200 dark:hover:bg-neutral-800">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="submit" class="inline-block px-6 pt-2.5 pb-2 ml-1 text-xs font-medium leading-normal text-white uppercase bg-rose-600 rounded transition duration-150 ease-in-out hover:bg-rose-700 focus:bg-rose-600" data-te-ripple-init data-te-ripple-color="light">
|
||||
<button
|
||||
type="submit"
|
||||
data-te-ripple-init
|
||||
class="rounded-xl bg-rose-600 px-5 py-2.5 text-sm font-semibold text-white shadow-lg shadow-rose-600/20 transition hover:bg-rose-700">
|
||||
Edit
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
@auth
|
||||
@if(Auth::user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR) || Auth::user()->hasRole(\App\Enums\UserRole::MODERATOR))
|
||||
<div class="relative p-5 bg-white dark:bg-neutral-700/40 rounded-lg overflow-hidden z-10">
|
||||
@if(Auth::user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
|
||||
<div class="float-left">
|
||||
<a data-te-toggle="modal" data-te-target="#modalUploadEpisode" class="text-xl text-gray-800 dark:text-gray-200 leading-tight cursor-pointer whitespace-nowrap">
|
||||
<i class="fa-solid fa-plus pr-[6px]"></i> Add Episode
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
<div class="float-right">
|
||||
@if(Auth::user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
|
||||
<a data-te-toggle="modal" data-te-target="#modalAddSubtitles" class="text-xl text-gray-800 dark:text-gray-200 leading-tight cursor-pointer whitespace-nowrap">
|
||||
<i class="fa-solid fa-plus pr-[6px]"></i> Add Subtitles
|
||||
</a>
|
||||
@endif
|
||||
<a data-te-toggle="modal" data-te-target="#modalEditEpisode" class="text-xl text-gray-800 dark:text-gray-200 leading-tight cursor-pointer whitespace-nowrap">
|
||||
<i class="fa-solid fa-pen pr-[6px]"></i> Edit Episode
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
@endauth
|
||||
@@ -28,6 +28,4 @@
|
||||
<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')
|
||||
</div>
|
||||
|
||||
@vite(['resources/js/responsive.js'])
|
||||
</x-app-layout>
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
@php
|
||||
$random = \cache()->remember('random_home', 300, function () {
|
||||
return \App\Models\Episode::inRandomOrder()->limit(7)->get(); ;
|
||||
return \App\Models\Episode::inRandomOrder()->limit(16)->get(); ;
|
||||
});
|
||||
@endphp
|
||||
|
||||
<div class="mb-6">
|
||||
@include('home.partials.tab.template', ['episodes' => $random, 'isThumbnail' => false, 'rowsCount' => 1])
|
||||
@include('home.partials.tab.template', ['episodes' => $random, 'isThumbnail' => false])
|
||||
</div>
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
@props([
|
||||
'isThumbnail',
|
||||
'rowsCount' => 2,
|
||||
])
|
||||
@props(['isThumbnail'])
|
||||
|
||||
@php
|
||||
$gridClasses = $isThumbnail
|
||||
? 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4'
|
||||
: 'grid-cols-1 sm:grid-cols-2 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-6 3xl:grid-cols-7';
|
||||
|
||||
// Render enough items for largest possible layout
|
||||
$limit = 24;
|
||||
$limit = 16;
|
||||
|
||||
$view = $isThumbnail ? 'thumbnail' : 'poster';
|
||||
@endphp
|
||||
|
||||
@if ($isThumbnail)
|
||||
<div
|
||||
class="episode-grid grid {{ $gridClasses }}"
|
||||
data-rows="{{ $rowsCount }}"
|
||||
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3 2xl:grid-cols-4 3xl:grid-cols-5
|
||||
[&>.episode-item]:hidden
|
||||
[&>.episode-item:nth-child(-n+8)]:block
|
||||
md:[&>.episode-item:nth-child(-n+8)]:block
|
||||
lg:[&>.episode-item:nth-child(-n+9)]:block
|
||||
xl:[&>.episode-item:nth-child(-n+9)]:block
|
||||
2xl:[&>.episode-item:nth-child(-n+12)]:block
|
||||
3xl:[&>.episode-item:nth-child(-n+15)]:block"
|
||||
>
|
||||
@else
|
||||
<div
|
||||
class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-4 2xl:grid-cols-5 3xl:grid-cols-8
|
||||
[&>.episode-item]:hidden
|
||||
[&>.episode-item:nth-child(-n+12)]:block
|
||||
md:[&>.episode-item:nth-child(-n+12)]:block
|
||||
xl:[&>.episode-item:nth-child(-n+12)]:block
|
||||
2xl:[&>.episode-item:nth-child(-n+15)]:block
|
||||
3xl:[&>.episode-item:nth-child(-n+16)]:block"
|
||||
>
|
||||
@endif
|
||||
@foreach ($episodes->take($limit) as $ep)
|
||||
@php
|
||||
$episode = isset($popularView)
|
||||
|
||||
@@ -8,10 +8,18 @@
|
||||
</div>
|
||||
<div class="flex flex-col text-center w-full">
|
||||
@if($fillNumbers)
|
||||
@if($version)
|
||||
<p class="text-lg">Episode {{ str_pad($episodeNumber, 2, '0', STR_PAD_LEFT) }} ({{ $version }})</p>
|
||||
@else
|
||||
<p class="text-lg">Episode {{ str_pad($episodeNumber, 2, '0', STR_PAD_LEFT) }}</p>
|
||||
@endif
|
||||
@else
|
||||
@if($version)
|
||||
<p class="text-lg">Episode {{ $episodeNumber }} ({{ $version }})</p>
|
||||
@else
|
||||
<p class="text-lg">Episode {{ $episodeNumber }}</p>
|
||||
@endif
|
||||
@endif
|
||||
<p class="text-xs">{{ $fileExtension }} MKV {{ $fileSize ?? '' }}</p>
|
||||
<p class="text-xs" id="count-{{ $downloadId }}">Downloaded {{ $downloadCount }} times</p>
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
@php
|
||||
$download = $hdl->getDownloadByType('FHDi');
|
||||
$downloadURL = $dldomains[array_rand($dldomains)].'/'.$download->url;
|
||||
$version = str_contains($download->url, 'v2') ? 'v2' : '';
|
||||
@endphp
|
||||
|
||||
<livewire:download-button
|
||||
@@ -14,5 +15,6 @@
|
||||
:download-count="$download->count"
|
||||
:episode-number="$hdl->episode"
|
||||
:fill-numbers="$fillNumbers"
|
||||
:file-size="$download->getFileSize()">
|
||||
:file-size="$download->getFileSize()"
|
||||
:version="$version">
|
||||
@endif
|
||||
@@ -10,6 +10,7 @@
|
||||
$now = \Illuminate\Support\Carbon::now();
|
||||
$expire = \Illuminate\Support\Facades\Crypt::encryptString($now->addHours(6));
|
||||
$file = \Illuminate\Support\Facades\Crypt::encryptString('hentai/'.$download->url);
|
||||
$version = str_contains($download->url, 'v2') ? 'v2' : '';
|
||||
|
||||
$downloadURL = $dlpdomains[array_rand($dlpdomains)].'/download/'.$file.'/'.$expire;
|
||||
@endphp
|
||||
@@ -20,5 +21,6 @@
|
||||
:download-count="$download->count"
|
||||
:episode-number="$hdl->episode"
|
||||
:fill-numbers="$fillNumbers"
|
||||
:file-size="$download->getFileSize()">
|
||||
:file-size="$download->getFileSize()"
|
||||
:version="$version">
|
||||
@endif
|
||||
@@ -10,6 +10,7 @@
|
||||
$now = \Illuminate\Support\Carbon::now();
|
||||
$expire = \Illuminate\Support\Facades\Crypt::encryptString($now->addHours(6));
|
||||
$file = \Illuminate\Support\Facades\Crypt::encryptString('hentai/'.$download->url);
|
||||
$version = str_contains($download->url, 'v2') ? 'v2' : '';
|
||||
|
||||
$downloadURL = $dlpdomains[array_rand($dlpdomains)].'/download/'.$file.'/'.$expire;
|
||||
@endphp
|
||||
@@ -20,5 +21,6 @@
|
||||
:download-count="$download->count"
|
||||
:episode-number="$hdl->episode"
|
||||
:fill-numbers="$fillNumbers"
|
||||
:file-size="$download->getFileSize()">
|
||||
:file-size="$download->getFileSize()"
|
||||
:version="$version">
|
||||
@endif
|
||||
@@ -3,7 +3,7 @@
|
||||
<div class="pt-6">
|
||||
<div class="flex flex-col lg:flex-row justify-center">
|
||||
<div class="pt-2 sm:px-2 lg:px-4 space-y-6 max-w-[100%] xl:max-w-[70%] 2xl:max-w-[60%] z-10">
|
||||
@include('series.partials.info')
|
||||
@include('stream.partials.info', ['streamPage' => false])
|
||||
|
||||
@include('series.partials.episodes')
|
||||
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<div class="bg-transparent rounded-lg overflow-hidden bg-white dark:bg-neutral-800 p-5">
|
||||
<a class="leading-normal font-bold text-lg text-rose-600">
|
||||
<div class="overflow-hidden rounded-2xl border border-gray-200/70 bg-white/90 shadow-sm backdrop-blur-sm transition-colors dark:border-white/10 dark:bg-neutral-900/80">
|
||||
<div class="p-5 md:p-7">
|
||||
<a class="text-lg font-bold text-gray-900 dark:text-white">
|
||||
{{ __('home.episodes') }} ({{ $hentai->episodes->count() }})
|
||||
</a>
|
||||
|
||||
<!-- Episode List -->
|
||||
<div class="grid grid-cols-1 sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-2">
|
||||
<div class="grid grid-cols-1 sm:grid-cols-1 md:grid-cols-1 lg:grid-cols-2 xl:grid-cols-3 2xl:grid-cols-4 gap-2 mt-4">
|
||||
@foreach ($hentai->episodes as $episode)
|
||||
<x-episode-cover :episode="$episode" view="thumbnail" />
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,69 +0,0 @@
|
||||
<div class="bg-transparent rounded-lg overflow-hidden bg-white dark:bg-neutral-800 p-5">
|
||||
<!-- Cover -->
|
||||
<div class="w-[100px] md:w-[150px] mr-4 float-left">
|
||||
<img alt="{{ $episode->title }}" loading="lazy" width="150"
|
||||
class="block relative rounded-lg object-cover object-center aspect-[11/16] z-20"
|
||||
src="{{ $episode->cover_url }}"></img>
|
||||
</div>
|
||||
|
||||
<!-- Info -->
|
||||
<div class="relative">
|
||||
<!-- Title -->
|
||||
<h1 class="text-3xl font-bold text-rose-600">
|
||||
{{ $episode->title }} ({{ $episode->title_jpn }})
|
||||
</h1>
|
||||
<div>
|
||||
<a data-te-toggle="tooltip"
|
||||
title="Released {{ \Carbon\Carbon::parse($episode->release_date)->diffForHumans(['parts' => 2]) }}"
|
||||
class="text-l text-gray-800 dark:text-white leading-tight pl-1">
|
||||
<i class="fa-regular fa-calendar"></i> {{ $episode->release_date }}
|
||||
|
|
||||
</a>
|
||||
<a href="{{ route('hentai.search', ['order' => 'recently-uploaded', 'studios[0]' => $episode->studio->slug]) }}"
|
||||
class="text-l text-gray-800 dark:text-white leading-tight hover:underline hover:underline-offset-4">
|
||||
{{ $episode->studio->name }}
|
||||
</a>
|
||||
</div>
|
||||
<hr class="border-gray-400/40 mt-2 mb-2">
|
||||
<p class="leading-normal font-bold text-lg text-rose-600">
|
||||
{{ __('stream.description') }}
|
||||
</p>
|
||||
<p class="text-gray-800 dark:text-gray-200 leading-tight min-h-[50%]">
|
||||
{{ $hentai->description }}
|
||||
</p>
|
||||
<hr class="border-gray-400/40 mt-2 mb-1">
|
||||
<ul class="list-none text-center" style="overflow: hidden;">
|
||||
<a class="text-gray-400">
|
||||
|
|
||||
</a>
|
||||
@foreach ($episode->tags->sortBy('slug') as $tag)
|
||||
<li class="inline-block p-1">
|
||||
@if ($tag->slug == 'uncensored' || $tag->slug == 'vanilla' || $tag->slug == '4k')
|
||||
<a href="{{ route('hentai.search', ['order' => 'recently-uploaded', 'tags[0]' => $tag->slug]) }}"
|
||||
class="relative block items-center px-2 py-2 mt-1 dark:focus:ring-offset-gray-800 border border-transparent rounded-md font-semibold text-xs text-green-500 dark:hover:text-white hover:text-white uppercase tracking-widest hover:bg-green-700 focus:bg-green-700 active:bg-green-900 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 transition ease-in-out duration-150">
|
||||
{{ $tag->name }}
|
||||
</a>
|
||||
@elseif($tag->slug == 'censored')
|
||||
<a href="{{ route('hentai.search', ['order' => 'recently-uploaded', 'tags[0]' => $tag->slug]) }}"
|
||||
class="relative block items-center px-2 py-2 mt-1 dark:focus:ring-offset-gray-800 border border-transparent rounded-md font-semibold text-xs text-yellow-600 dark:hover:text-white hover:text-white uppercase tracking-widest hover:bg-yellow-700 focus:bg-yellow-700 active:bg-yellow-900 focus:outline-none focus:ring-2 focus:ring-yellow-500 focus:ring-offset-2 transition ease-in-out duration-150">
|
||||
{{ $tag->name }}
|
||||
</a>
|
||||
@elseif($tag->slug == 'gore' || $tag->slug == 'horror' || $tag->slug == 'scat' || $tag->slug == 'ntr' || $tag->slug == 'rape')
|
||||
<a href="{{ route('hentai.search', ['order' => 'recently-uploaded', 'tags[0]' => $tag->slug]) }}"
|
||||
class="relative block items-center px-2 py-2 mt-1 dark:focus:ring-offset-gray-800 border border-transparent rounded-md font-semibold text-xs text-red-600 dark:hover:text-white hover:text-white uppercase tracking-widest hover:bg-red-700 focus:bg-red-700 active:bg-red-900 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition ease-in-out duration-150">
|
||||
<i class="fa-solid fa-triangle-exclamation"></i> {{ $tag->name }}
|
||||
</a>
|
||||
@else
|
||||
<a href="{{ route('hentai.search', ['order' => 'recently-uploaded', 'tags[0]' => $tag->slug]) }}"
|
||||
class="relative block items-center px-2 py-2 mt-1 dark:focus:ring-offset-gray-800 border border-transparent rounded-md font-semibold text-xs dark:text-white hover:text-white uppercase tracking-widest hover:bg-rose-700 focus:bg-rose-700 active:bg-rose-900 focus:outline-none focus:ring-2 focus:ring-rose-500 focus:ring-offset-2 transition ease-in-out duration-150">
|
||||
{{ $tag->name }}
|
||||
</a>
|
||||
@endif
|
||||
</li>
|
||||
<a class="text-gray-400">
|
||||
|
|
||||
</a>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
@@ -15,7 +15,6 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@include('admin.stream')
|
||||
<!-- Infos -->
|
||||
@include('stream.partials.info')
|
||||
<!-- Comments -->
|
||||
@@ -38,18 +37,18 @@
|
||||
@include('modals.add-to-playlist')
|
||||
@include('modals.share')
|
||||
|
||||
|
||||
@auth
|
||||
|
||||
@if(Auth::user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR) || Auth::user()->hasRole(\App\Enums\UserRole::MODERATOR))
|
||||
@include('admin.modals.edit-episode')
|
||||
@endif
|
||||
|
||||
@if(Auth::user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
|
||||
@if (auth()->user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
|
||||
@include('admin.modals.upload-episode')
|
||||
@include('admin.modals.add-subtitles')
|
||||
@endif
|
||||
|
||||
@if (auth()->user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR) || auth()->user()->hasRole(\App\Enums\UserRole::MODERATOR))
|
||||
@include('admin.modals.edit-episode')
|
||||
@endif
|
||||
@endauth
|
||||
|
||||
<!-- Player Script -->
|
||||
@vite(['resources/js/player.js'])
|
||||
</x-app-layout>
|
||||
|
||||
@@ -1,8 +1,14 @@
|
||||
@props([
|
||||
'streamPage' => true,
|
||||
])
|
||||
|
||||
<div
|
||||
class="overflow-hidden rounded-2xl border border-gray-200/70 bg-white/90 shadow-sm backdrop-blur-sm transition-colors dark:border-white/10 dark:bg-neutral-900/80">
|
||||
|
||||
<div class="p-5 md:p-7">
|
||||
@if($streamPage)
|
||||
<input id="e_id" type="hidden" value="{{ $episode->id }}" />
|
||||
@endif
|
||||
|
||||
<div class="flex flex-col gap-6 lg:flex-row">
|
||||
<!-- Cover -->
|
||||
@@ -23,13 +29,21 @@
|
||||
<div class="min-w-0">
|
||||
<h1
|
||||
class="break-words text-2xl font-black tracking-tight text-gray-900 dark:text-white md:text-4xl">
|
||||
@if ($streamPage)
|
||||
<a
|
||||
href="{{ route('hentai.index', ['title' => $episode->hentai->slug]) }}"
|
||||
class="bg-gradient-to-r from-rose-500 to-pink-500 bg-clip-text text-transparent transition hover:opacity-80">
|
||||
{{ $episode->title }} - {{ $episode->episode }}
|
||||
{{ "$episode->title - $episode->episode" }}
|
||||
</a>
|
||||
@else
|
||||
<span
|
||||
class="bg-gradient-to-r from-rose-500 to-pink-500 bg-clip-text text-transparent transition">
|
||||
{{ $episode->title }}
|
||||
</span>
|
||||
@endif
|
||||
</h1>
|
||||
|
||||
|
||||
<p class="mt-2 text-sm text-gray-500 dark:text-gray-400 md:text-base">
|
||||
{{ $episode->title_jpn }}
|
||||
</p>
|
||||
@@ -65,6 +79,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($streamPage)
|
||||
<!-- Stats + Actions -->
|
||||
<div class="flex flex-col gap-3 xl:items-end min-w-[330px]">
|
||||
|
||||
@@ -133,7 +148,38 @@
|
||||
</a>
|
||||
@endauth
|
||||
</div>
|
||||
|
||||
@if(auth()->check() && (auth()->user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR) || auth()->user()->hasRole(\App\Enums\UserRole::MODERATOR)))
|
||||
<div class="flex flex-wrap flex-row-reverse gap-2">
|
||||
@if(auth()->user()->hasRole(\App\Enums\UserRole::ADMINISTRATOR))
|
||||
<a
|
||||
data-te-toggle="modal"
|
||||
data-te-target="#modalAddSubtitles"
|
||||
class="inline-flex cursor-pointer items-center gap-2 rounded-xl border border-gray-300 bg-white px-4 py-2 text-sm font-semibold text-gray-700 transition hover:bg-gray-100 dark:border-white/10 dark:bg-white/5 dark:text-gray-200 dark:hover:bg-white/10">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
Add Subtitles
|
||||
</a>
|
||||
|
||||
<a
|
||||
data-te-toggle="modal"
|
||||
data-te-target="#modalUploadEpisode"
|
||||
class="inline-flex cursor-pointer items-center gap-2 rounded-xl border border-gray-300 bg-white px-4 py-2 text-sm font-semibold text-gray-700 transition hover:bg-gray-100 dark:border-white/10 dark:bg-white/5 dark:text-gray-200 dark:hover:bg-white/10">
|
||||
<i class="fa-solid fa-plus"></i>
|
||||
Add Episode
|
||||
</a>
|
||||
@endif
|
||||
|
||||
<a
|
||||
data-te-toggle="modal"
|
||||
data-te-target="#modalEditEpisode"
|
||||
class="inline-flex cursor-pointer items-center gap-2 rounded-xl border border-gray-300 bg-white px-4 py-2 text-sm font-semibold text-gray-700 transition hover:bg-gray-100 dark:border-white/10 dark:bg-white/5 dark:text-gray-200 dark:hover:bg-white/10">
|
||||
<i class="fa-solid fa-pen"></i>
|
||||
Edit Episode
|
||||
</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
@@ -189,9 +235,11 @@
|
||||
</div>
|
||||
|
||||
<!-- Gallery -->
|
||||
@if($streamPage)
|
||||
<div class="mt-8 border-t border-gray-200 pt-6 dark:border-white/10">
|
||||
@include('stream.partials.gallery')
|
||||
</div>
|
||||
@endisset
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+4
-1
@@ -45,7 +45,7 @@ Route::group(['middleware' => ['auth', 'auth.admin']], function () {
|
||||
|
||||
// Episode
|
||||
Route::post('/admin/episode/upload', [EpisodeController::class, 'store'])->name('admin.upload.episode');
|
||||
Route::post('/admin/episode/edit', [EpisodeController::class, 'update'])->name('admin.edit');
|
||||
|
||||
|
||||
// Get Tags used for Upload Form
|
||||
Route::get('/admin/tags', [AdminApiController::class, 'getTags'])->name('admin.tags');
|
||||
@@ -66,4 +66,7 @@ Route::group(['middleware' => ['auth', 'auth.moderator']], function () {
|
||||
// Get Tags for editing Episode
|
||||
Route::get('/admin/tags/{episode_id}', [AdminApiController::class, 'getEpisodeTags'])->name('admin.tags.episode');
|
||||
Route::get('/admin/studio/{episode_id}', [AdminApiController::class, 'getEpisodeStudio'])->name('admin.studio.episode');
|
||||
|
||||
// Edit Episode
|
||||
Route::post('/admin/episode/edit', [EpisodeController::class, 'update'])->name('admin.episode.edit');
|
||||
});
|
||||
+5
-3
@@ -2,6 +2,7 @@
|
||||
|
||||
use Illuminate\Foundation\Inspiring;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use Illuminate\Support\Facades\Schedule;
|
||||
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
@@ -14,6 +15,7 @@ use Illuminate\Support\Facades\Artisan;
|
||||
|
|
||||
*/
|
||||
|
||||
Artisan::command('inspire', function () {
|
||||
$this->comment(Inspiring::quote());
|
||||
})->purpose('Display an inspiring quote');
|
||||
Schedule::command('app:auto-stats')->hourly();
|
||||
Schedule::command('app:reset-user-downloads')->daily();
|
||||
Schedule::command('app:generate-sitemap')->daily();
|
||||
Schedule::command('app:sync-subscription-keys')->daily();
|
||||
|
||||
@@ -21,7 +21,6 @@ export default defineConfig({
|
||||
'resources/js/admin-edit.js',
|
||||
'resources/js/admin-subtitles.js',
|
||||
'resources/js/preview.js',
|
||||
'resources/js/responsive.js',
|
||||
'resources/js/stats.js'
|
||||
],
|
||||
refresh: true,
|
||||
|
||||
Reference in New Issue
Block a user