72 Commits

Author SHA1 Message Date
65c7721228 Merge pull request 'dev' (#32) from dev into master
Reviewed-on: #32
2026-07-01 16:00:26 +00:00
1dff66469c Merge branch 'master' into dev 2026-07-01 16:00:22 +00:00
04b9db3359 Merge remote-tracking branch 'origin/dev' into dev 2026-07-01 17:59:54 +02:00
77f777b647 Added manual hashes. 2026-07-01 17:59:50 +02:00
870749d9b7 Merge pull request 'dev' (#31) from dev into master
Reviewed-on: #31
2026-07-01 13:11:56 +00:00
550e7f7214 Merge branch 'master' into dev 2026-07-01 13:11:53 +00:00
e42f0852b1 Merge remote-tracking branch 'origin/dev' into dev 2026-07-01 15:11:24 +02:00
6ace975ab8 Fixed encoded characters. 2026-07-01 15:11:18 +02:00
3bd3619c69 Merge pull request 'dev' (#30) from dev into master
Reviewed-on: #30
2026-07-01 11:44:48 +00:00
9ec774f01e Merge branch 'master' into dev 2026-07-01 11:44:44 +00:00
db2d336b24 Merge remote-tracking branch 'origin/dev' into dev 2026-07-01 13:43:32 +02:00
ee767a433b Added helper to description field. 2026-07-01 13:41:58 +02:00
0c7f8d001d Improve topbar 2026-07-01 13:37:28 +02:00
69af7777d2 Merge pull request 'dev' (#29) from dev into master
Reviewed-on: #29
2026-07-01 10:26:50 +00:00
e7a2a99dba Merge branch 'master' into dev 2026-07-01 10:26:46 +00:00
c413491171 Merge remote-tracking branch 'origin/dev' into dev 2026-07-01 12:26:23 +02:00
a6d8d924f1 Fixed another CSS problems 2026-07-01 12:26:19 +02:00
c3f13a23af Merge pull request 'dev' (#28) from dev into master
Reviewed-on: #28
2026-07-01 09:56:52 +00:00
4c84287274 Merge branch 'master' into dev 2026-07-01 09:56:48 +00:00
2c1677692f Merge remote-tracking branch 'origin/dev' into dev 2026-07-01 11:56:26 +02:00
ee5b2f6e09 Fixed another CSS problems 2026-07-01 11:56:22 +02:00
fda7aca4f9 Merge pull request 'dev' (#27) from dev into master
Reviewed-on: #27
2026-07-01 09:51:48 +00:00
ed91375514 Merge branch 'master' into dev 2026-07-01 09:51:44 +00:00
d2a2027c42 Merge remote-tracking branch 'origin/dev' into dev 2026-07-01 11:51:35 +02:00
b422cd2c82 Fixed a lot of responsive problems.
- Fixed auth problem
- Fixed BBCode and Markdown apparition in some unnecessary parts
2026-07-01 11:51:30 +02:00
f5a90aea4d Merge pull request 'dev' (#26) from dev into master
Reviewed-on: #26
2026-06-30 18:08:02 +00:00
dff376217f Merge branch 'master' into dev 2026-06-30 18:07:58 +00:00
55a8154b52 Merge remote-tracking branch 'origin/dev' into dev 2026-06-30 20:07:42 +02:00
8ca82fd0c8 Fixed Guzzle 30000ms error and improve cache usage 2026-06-30 20:07:38 +02:00
837b3de163 Merge pull request 'dev' (#25) from dev into master
Reviewed-on: #25
2026-06-30 17:46:05 +00:00
71014349aa Merge branch 'master' into dev 2026-06-30 17:46:01 +00:00
aefe03ed67 Merge remote-tracking branch 'origin/dev' into dev 2026-06-30 19:45:22 +02:00
1afe77415d Fixed 500 error on form error 2026-06-30 19:45:16 +02:00
f492a4d02f Fixed Marks all and added total downloads to entries.show 2026-06-30 19:07:57 +02:00
0c0a6a401e Merge pull request 'dev' (#24) from dev into master
Reviewed-on: #24
2026-06-30 15:49:34 +00:00
bdc4158dec Merge branch 'master' into dev 2026-06-30 15:48:36 +00:00
0ecb4610eb Merge remote-tracking branch 'origin/dev' into dev 2026-06-30 17:48:14 +02:00
d75d76ef6e Fix News aside 2026-06-30 17:48:09 +02:00
dc6df8b02a Merge pull request 'dev' (#23) from dev into master
Reviewed-on: #23
2026-06-30 14:13:09 +00:00
a91106ddf7 Merge branch 'master' into dev 2026-06-30 14:13:05 +00:00
53b611bb31 Merge remote-tracking branch 'origin/dev' into dev 2026-06-30 16:12:22 +02:00
7e1f8fc55a Fix XF News comment 2026-06-30 16:12:17 +02:00
f71c51f4ba Merge pull request 'dev' (#22) from dev into master
Reviewed-on: #22
2026-06-30 12:52:20 +00:00
ef24a16132 Merge branch 'master' into dev 2026-06-30 12:52:14 +00:00
3b0051585c Merge remote-tracking branch 'origin/dev' into dev 2026-06-30 14:52:00 +02:00
a52db45972 Quick fix 2026-06-30 14:51:56 +02:00
487b2e2956 Merge pull request 'dev' (#21) from dev into master
Reviewed-on: #21
2026-06-30 12:07:01 +00:00
74a678edda Merge branch 'master' into dev 2026-06-30 12:06:57 +00:00
dc577f5c8b Merge remote-tracking branch 'origin/dev' into dev 2026-06-30 14:06:18 +02:00
22893fd957 Final changes 2026-06-30 14:06:11 +02:00
176a883633 Fix profile picture not aligned 2026-06-29 19:39:10 +02:00
3cb98ce542 Fix download sort 2026-06-29 19:35:53 +02:00
300c417f3c Merge pull request 'dev' (#20) from dev into master
Reviewed-on: #20
2026-06-29 17:30:56 +00:00
dcf99a3351 Merge branch 'master' into dev 2026-06-29 17:30:50 +00:00
1bc89691d9 Merge remote-tracking branch 'origin/dev' into dev 2026-06-29 19:24:40 +02:00
c76bf45904 Fixed several bugs 2026-06-29 19:24:35 +02:00
3cdb6f88b8 Merge pull request 'dev' (#19) from dev into master
Reviewed-on: #19
2026-06-29 15:49:08 +00:00
378c34b8c0 Merge branch 'master' into dev 2026-06-29 15:49:01 +00:00
73844b6db0 Merge remote-tracking branch 'origin/dev' into dev 2026-06-29 17:48:50 +02:00
f47528a2a1 Fix LUA error 2026-06-29 17:46:15 +02:00
6977d5ad57 Merge pull request 'dev' (#18) from dev into master
Reviewed-on: #18
2026-06-29 13:01:52 +00:00
0009d3339e Merge branch 'master' into dev 2026-06-29 13:01:45 +00:00
93acbb4325 Merge remote-tracking branch 'origin/dev' into dev 2026-06-29 15:01:20 +02:00
e9fdc9be51 Finish grids 2026-06-29 14:58:08 +02:00
a1ca5b084a Merge pull request 'dev' (#17) from dev into master
Reviewed-on: #17
2026-06-29 10:42:28 +00:00
60dfa1588a Merge branch 'master' into dev 2026-06-29 10:42:24 +00:00
60ce1cf400 Merge remote-tracking branch 'origin/dev' into dev 2026-06-29 12:42:03 +02:00
a9158c722a Stop tracking config/xenforo.php 2026-06-29 12:41:45 +02:00
848c528de3 Merge pull request 'Add maintenance pages' (#16) from dev into master
Reviewed-on: #16
2026-06-29 10:39:07 +00:00
d163d24780 Merge branch 'master' into dev 2026-06-29 10:38:37 +00:00
05e7d4754f Merge remote-tracking branch 'origin/dev' into dev 2026-06-29 11:47:29 +02:00
1abfa96c2c Add maintenance pages 2026-06-29 11:47:19 +02:00
82 changed files with 2106 additions and 687 deletions

1
.gitignore vendored
View File

@@ -30,3 +30,4 @@ avancee.ods
rhpz.local+1.pem rhpz.local+1.pem
rhpz.local+1-key.pem rhpz.local+1-key.pem
extra.less extra.less
config/xenforo.php

View File

@@ -64,6 +64,19 @@ class XenForoGuard implements Guard
if ($this->hasUser()) if ($this->hasUser())
return $this->user; return $this->user;
$user = $this->getFromSession();
if( $user )
return $user;
$user = $this->getFromCookie();
if( $user )
return $user;
return null;
}
private function getFromSession(): ?XenForoUser
{
$sessionId = $this->request->cookie('xf_session'); $sessionId = $this->request->cookie('xf_session');
if(!$sessionId) if(!$sessionId)
return null; return null;
@@ -92,6 +105,64 @@ class XenForoGuard implements Guard
return $this->user = new XenForoUser($xfUser); return $this->user = new XenForoUser($xfUser);
} }
private function isCorrectCookieKey(string $key, $record): bool
{
$known = $record->remember_key;
if( !$known )
return false;
$check = hash('sha256', $key, true);
return hash_equals($known, $check);
}
private function getFromCookie(): ?XenForoUser
{
$cookie = $this->request->cookie('xf_user');
if(!$cookie)
return null;
$parts = explode(',', $cookie);
if( count( $parts ) !== 2 )
return null;
[$userId, $key] = $parts;
$userId = (int) $userId;
if( !$userId || !$key )
return null;
$remembers = \DB::connection('xenforo')
->table('user_remember')
->where('user_id', $userId)
->get();
if( !$remembers )
return null;
$valid = false;
foreach( $remembers as $remember )
{
if( $this->isCorrectCookieKey($key, $remember) && $remember->expiry_date >= time() ){
$valid = true;
break;
}
}
if( !$valid )
return null;
$xfUser = \DB::connection('xenforo')
->table('user')
->where('user_id', $userId)
->first();
if(!$xfUser)
return null;
return $this->user = new XenForoUser($xfUser);
}
/** /**
* Unused. * Unused.
* *

View File

@@ -0,0 +1,65 @@
<?php
namespace App\Console\Commands;
use App\Helpers\MigrationHelpers;
use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Command;
use App\Models\Entry;
use Illuminate\Support\Facades\DB;
use League\HTMLToMarkdown\HtmlConverter;
#[Signature('fix:entries-encoded-description {--dry-run}')]
#[Description('Remove coded specials chars in description')]
class FixEntriesEncodedDescription extends Command
{
public function handle(): int
{
$dryRun = $this->option('dry-run');
$fixed = 0;
Entry::withTrashed()
->withoutGlobalScopes()
->chunkById(100, function ($entries) use (&$fixed, $dryRun) {
foreach ($entries as $entry) {
$original = $entry->description;
if ($original === null || $original === '') {
continue;
}
$decoded = $original;
for ($i = 0; $i < 20; $i++) {
$next = html_entity_decode($decoded, ENT_QUOTES | ENT_HTML5);
if ($next === $decoded) {
break;
}
$decoded = $next;
}
$decoded = stripslashes($decoded);
if ($decoded !== $original) {
$fixed++;
$this->line("Entry #{$entry->id}: " . $this->preview($original) . ' => ' . $this->preview($decoded));
if (!$dryRun) {
$entry->description = $decoded;
$entry->saveQuietly();
}
}
}
});
$this->info(($dryRun ? '[DRY RUN] ' : '') . "{$fixed} entries fixed.).");
return self::SUCCESS;
}
private function preview(string $text, int $len = 60): string
{
$text = str_replace(["\n", "\r"], ' ', $text);
return mb_strlen($text) > $len ? mb_substr($text, 0, $len) . '...' : $text;
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\DB;
#[Signature('migrate:downloads-count')]
#[Description('Command description')]
class MigrateDownloadsCount extends Command
{
public function handle()
{
$rows = DB::table('migrations_logs')
->where('source_system', 'wp')
->where('source_table', 'wp_posts')
->where('target_table', 'entries' )
->get(['source_id', 'target_id'])
;
$updated = 0;
foreach ($rows as $row) {
$downloadCount = DB::connection('old_wp')
->table('postmeta')
->where('post_id', $row->source_id)
->where('meta_key', 'download_counter' )
->value('meta_value');
if( $downloadCount ) {
DB::table('entries')->where('id', $row->target_id)->update(['old_download_count' => intval($downloadCount)]);
$updated++;
}
}
$this->info('All done (' . $updated . '/' . count($rows) .')');
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
#[Signature('submissions:down')]
#[Description('Disable submissions')]
class SubmissionsDown extends Command
{
/**
* Execute the console command.
*/
public function handle()
{
Cache::put('submissions_maintenance', true );
$this->info("Submissions disabled.");
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
#[Signature('submissions:up')]
#[Description('Enable submissions')]
class SubmissionsUp extends Command
{
/**
* Execute the console command.
*/
public function handle()
{
Cache::forget('submissions_maintenance');
$this->info('Submissions enabled.');
}
}

View File

@@ -6,6 +6,8 @@ use App\Models\Entry;
use App\Models\EntryFile; use App\Models\EntryFile;
use App\Models\News; use App\Models\News;
use App\Services\XenforoApiService; use App\Services\XenforoApiService;
use GuzzleHttp\Exception\ConnectException;
use GuzzleHttp\Exception\RequestException;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str; use Illuminate\Support\Str;
@@ -74,8 +76,9 @@ class EntryHelpers {
$cacheKey = "news_comments_{$entry->id}"; $cacheKey = "news_comments_{$entry->id}";
else else
$cacheKey = "entry_comments_{$entry->id}"; $cacheKey = "entry_comments_{$entry->id}";
return Cache::remember($cacheKey, now()->addDays(1), function () use ($entry, $limit) { return Cache::flexible($cacheKey, [now()->addMinutes(30), now()->addMinutes(60)], function () use ($entry, $limit) {
try {
$service = app(XenforoApiService::class); $service = app(XenforoApiService::class);
// Get thread infos and pagination. // Get thread infos and pagination.
@@ -92,6 +95,9 @@ class EntryHelpers {
} }
return collect($posts)->slice(-$limit)->reverse()->values()->toArray(); return collect($posts)->slice(-$limit)->reverse()->values()->toArray();
} catch (ConnectException|RequestException $e ){
return [];
}
}); });
} }
@@ -119,4 +125,18 @@ class EntryHelpers {
session(["downloaded_file_{$entryFile->file_uuid}" => 1]); session(["downloaded_file_{$entryFile->file_uuid}" => 1]);
return true; return true;
} }
public static function stripBbCode(?string $text): ?string
{
if ($text === null) return null;
$text = preg_replace('/\[quote\b[^\]]*\](.*?)\[\/quote\]/is', '', $text);
return preg_replace('/\[\/?\w+[^\]]*\]/i', '', $text);
}
public static function stripMarkdown(?string $text): ?string
{
if ($text === null) return null;
$html = Str::markdown($text);
return html_entity_decode(strip_tags($html), ENT_QUOTES, 'UTF-8');
}
} }

View File

@@ -19,7 +19,7 @@ class AuthorController extends Controller
{ {
$items = Author::withCount('entries') $items = Author::withCount('entries')
->orderBy('name') ->orderBy('name')
->tap(fn($query) => $this->applySearch($query, ['name'])) ->tap(fn($query) => $this->applySearch($query, ['id','name']))
->paginate(30) ->paginate(30)
->withQueryString(); ->withQueryString();

View File

@@ -18,7 +18,7 @@ class GameController extends Controller
public function index() public function index()
{ {
$items = Game::withCount('entries')->orderBy('name') $items = Game::withCount('entries')->orderBy('name')
->tap(fn($query) => $this->applySearch($query, ['name'])) ->tap(fn($query) => $this->applySearch($query, ['id','name']))
->paginate(30)->withQueryString(); ->paginate(30)->withQueryString();
$platforms = Platform::orderBy('name')->get(); $platforms = Platform::orderBy('name')->get();
$genres = Genre::orderBy('name')->get(); $genres = Genre::orderBy('name')->get();

View File

@@ -18,7 +18,7 @@ class GenreController extends Controller
{ {
$items = Genre::withCount('games') $items = Genre::withCount('games')
->orderBy('name') ->orderBy('name')
->tap(fn($query) => $this->applySearch($query, ['name'])) ->tap(fn($query) => $this->applySearch($query, ['id','name']))
->paginate(30) ->paginate(30)
->withQueryString(); ->withQueryString();

View File

@@ -17,7 +17,7 @@ class LanguageController extends Controller
{ {
$items = Language::withCount('entries') $items = Language::withCount('entries')
->orderBy('name') ->orderBy('name')
->tap(fn($query) => $this->applySearch($query, ['name'])) ->tap(fn($query) => $this->applySearch($query, ['id','name']))
->paginate(30) ->paginate(30)
->withQueryString(); ->withQueryString();

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers\ModCP;
use App\Helpers\EntryHelpers;
use App\Http\Controllers\Controller;
use App\Models\Level;
use App\Traits\ModCPSearch;
use Illuminate\Http\Request;
class LevelController extends Controller
{
use ModCPSearch;
public function index()
{
$items = Level::withCount('entries')
->orderBy('name')
->tap(fn($query) => $this->applySearch($query, ['id','name']))
->paginate(30)
->withQueryString();
return view('modcp.resources', [
'items' => $items,
'title' => 'Levels',
'singular' => 'Level',
'storeRoute' => 'modcp.levels.store',
'updateRoute' => 'modcp.levels.update',
'destroyRoute' => 'modcp.levels.destroy'
]);
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255|unique:levels,name',
]);
Level::create([
'name' => trim($request->name),
'slug' => EntryHelpers::uniqueSlug( $request->name, Level::class ),
]);
return back()->with('success', 'Level added.');
}
public function update(Request $request, Level $level)
{
$request->validate([
'name' => 'required|string|max:255|unique:levels,name,' . $level->id,
]);
$level->update([
'name' => trim($request->name),
'slug' => EntryHelpers::uniqueSlug( $request->name, Level::class, $level->id ),
]);
return back()->with('success', 'Level updated.');
}
public function destroy(Level $level)
{
$level->delete();
return back()->with('success', 'Level deleted.');
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Http\Controllers\ModCP;
use App\Helpers\EntryHelpers;
use App\Http\Controllers\Controller;
use App\Models\Modification;
use App\Traits\ModCPSearch;
use Illuminate\Http\Request;
class ModificationsController extends Controller
{
use ModCPSearch;
public function index()
{
$items = Modification::withCount('entries')
->orderBy('name')
->tap(fn($query) => $this->applySearch($query, ['id','name']))
->paginate(30)
->withQueryString();
return view('modcp.resources', [
'items' => $items,
'title' => 'Modifications',
'singular' => 'Modification',
'storeRoute' => 'modcp.modifications.store',
'updateRoute' => 'modcp.modifications.update',
'destroyRoute' => 'modcp.modifications.destroy'
]);
}
public function store(Request $request)
{
$request->validate([
'name' => 'required|string|max:255|unique:modifications,name',
]);
Modification::create([
'name' => trim($request->name),
'slug' => EntryHelpers::uniqueSlug( $request->name, Modification::class ),
]);
return back()->with('success', 'Modification added.');
}
public function update(Request $request, Modification $Modification)
{
$request->validate([
'name' => 'required|string|max:255|unique:modifications,name,' . $Modification->id,
]);
$Modification->update([
'name' => trim($request->name),
'slug' => EntryHelpers::uniqueSlug( $request->name, Modification::class, $Modification->id ),
]);
return back()->with('success', 'Modification updated.');
}
public function destroy(Modification $Modification)
{
$Modification->delete();
return back()->with('success', 'Modification deleted.');
}
}

View File

@@ -18,7 +18,7 @@ class PlatformController extends Controller
{ {
$items = Platform::withCount(['games','entries']) $items = Platform::withCount(['games','entries'])
->orderBy('name') ->orderBy('name')
->tap(fn($query) => $this->applySearch($query, ['name'])) ->tap(fn($query) => $this->applySearch($query, ['id','name']))
->paginate(30) ->paginate(30)
->withQueryString(); ->withQueryString();

View File

@@ -78,8 +78,8 @@ class SubmissionController extends Controller
'bg' => '#f3eaff1a', 'bg' => '#f3eaff1a',
'border' => '#f3eaff40', 'border' => '#f3eaff40',
], ],
'lua-script' => [ 'lua-scripts' => [
'slug' => 'lua-script', 'slug' => 'lua-scripts',
'label' => 'Lua script', 'label' => 'Lua script',
'icon' => 'terminal', 'icon' => 'terminal',
'color' => '#a04515', 'color' => '#a04515',

View File

@@ -0,0 +1,33 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response;
class SubmissionsEnabled
{
/**
* Handle an incoming request.
*
* @param Closure(Request): (Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
if( Cache::get('submissions_maintenance', false ) ){
if( $request->expectsJson() ){
return response()->json([
'message' => 'The submissions are currently in maintenance mode.',
], 503 );
}
return back()->withErrors([
'maintenance' => "The submissions are currently in maintenance mode.",
])->withInput();
}
return $next($request);
}
}

View File

@@ -175,6 +175,7 @@ class Database extends Component
public const array SORT_OPTIONS = [ public const array SORT_OPTIONS = [
'created_at' => 'Date added', 'created_at' => 'Date added',
'release_date' => 'Release date', 'release_date' => 'Release date',
'total_downloads' => 'Total downloads',
'title' => 'Title' 'title' => 'Title'
]; ];
@@ -236,7 +237,7 @@ class Database extends Component
{ {
$query = Entry::query()->published()->with([ $query = Entry::query()->published()->with([
'game.platform', 'game.genre', 'status', 'authors', 'languages', 'level', 'systems', 'categories', 'modifications' 'game.platform', 'game.genre', 'status', 'authors', 'languages', 'level', 'systems', 'categories', 'modifications'
]); ])->withSum('files', 'download_count');
if( $this->search ) { if( $this->search ) {
$query->where(function($q) { $query->where(function($q) {
@@ -328,7 +329,13 @@ class Database extends Component
$query->where('user_id', $this->userId); $query->where('user_id', $this->userId);
} }
return $query->orderBy($this->sortBy, $this->sortDir); $sortColumn = $this->sortBy;
if ($sortColumn === 'total_downloads') {
return $query->orderByRaw('COALESCE(files_sum_download_count, 0) + old_download_count ' . $this->sortDir);
}
return $query->orderBy($sortColumn, $this->sortDir);
} }
private function searchFilter( string $modelClass, string $search ) private function searchFilter( string $modelClass, string $search )

View File

@@ -21,6 +21,7 @@ class EntrySelector extends Component
if( $entry ) { if( $entry ) {
$this->selectedEntryId = $oldEntryId; $this->selectedEntryId = $oldEntryId;
$this->entryName = $entry->complete_title ?? $entry->title; $this->entryName = $entry->complete_title ?? $entry->title;
$this->search = $entry->complete_title ?? $entry->title;
} }
} }
} }

View File

@@ -25,6 +25,10 @@ class HashesUpload extends Component
*/ */
public array $hashes = []; public array $hashes = [];
public ?string $manualFilename = "";
public ?string $manualCRC32 = "";
public ?string $manualSHA1 = "";
/** /**
* Prepare old hashes. * Prepare old hashes.
* *
@@ -87,6 +91,41 @@ class HashesUpload extends Component
array_splice($this->hashes, $index, 1); array_splice($this->hashes, $index, 1);
} }
private function checkCrc32($attribute, $value, $fail)
{
if (!preg_match('/^[a-f0-9]{8}$/i', $value)) {
$fail("CRC32 is invalid");
}
}
private function checkSha1($attribute, $value, $fail)
{
if (!preg_match('/^[a-f0-9]{40}$/i', $value)) {
$fail("SHA1 is invalid");
}
}
public function addManualHash()
{
$this->validate([
'manualFilename' => "required|string|max:512",
'manualCRC32' => ['required', 'string', 'max:512', $this->checkCrc32(...) ],
'manualSHA1' => ['required', 'string', 'max:512', $this->checkSha1(...) ],
], [
'manualFilename.required' => 'Please enter a filename.',
'manualCRC32.required' => 'Please enter a crc32.',
'manualSHA1.required' => 'Please enter a SHA1.',
'manualFilename.max' => 'Filename has to be less than 512 characters.',
'manualCRC32.max' => 'CRC32 has to be less than 512 characters.',
'manualSHA1.max' => 'SHA1 has to be less than 512 characters.',
]);
$this->addHash( $this->manualFilename, $this->manualCRC32, $this->manualSHA1 );
$this->reset(['manualFilename','manualCRC32','manualSHA1']);
$this->dispatch('close-manual-modal');
}
public function render(): View public function render(): View
{ {

View File

@@ -7,6 +7,8 @@ use App\Services\XenforoService;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
use Spatie\Activitylog\Support\LogOptions;
/** /**
* @property int $id * @property int $id
@@ -32,6 +34,9 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
*/ */
class Author extends Model class Author extends Model
{ {
use LogsActivity;
protected $fillable = [ protected $fillable = [
'name', 'slug', 'user_id', 'website' 'name', 'slug', 'user_id', 'website'
]; ];
@@ -49,4 +54,14 @@ class Author extends Model
return app(XenforoService::class)->getXfUser($this->user_id); return app(XenforoService::class)->getXfUser($this->user_id);
} }
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->useLogName('author')
->logAll()
->logOnlyDirty()
->dontLogEmptyChanges()
->setDescriptionForEvent(fn(string $eventName) => "Author {$eventName}");
}
} }

View File

@@ -253,6 +253,11 @@ class Entry extends Model
return $converter->convert($this->description)->getContent(); return $converter->convert($this->description)->getContent();
} }
public function getTotalDownloadsAttribute(): int
{
return $this->files->sum('download_count') + $this->old_download_count;
}
public function parseStaffCredits(): ?array { public function parseStaffCredits(): ?array {
return json_decode( $this->staff_credits ?? "", true ); return json_decode( $this->staff_credits ?? "", true );
} }

View File

@@ -8,6 +8,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use League\CommonMark\GithubFlavoredMarkdownConverter; use League\CommonMark\GithubFlavoredMarkdownConverter;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
use Spatie\Activitylog\Support\LogOptions;
/** /**
* @property int $id * @property int $id
@@ -41,7 +43,7 @@ use League\CommonMark\GithubFlavoredMarkdownConverter;
class EntryReview extends Model class EntryReview extends Model
{ {
use HasXenforoUserId, SoftDeletes; use HasXenforoUserId, SoftDeletes, LogsActivity;
protected $fillable = [ 'entry_id', 'title', 'rating', 'description', 'user_id' ]; protected $fillable = [ 'entry_id', 'title', 'rating', 'description', 'user_id' ];
@@ -60,4 +62,14 @@ class EntryReview extends Model
return $converter->convert($this->description)->getContent(); return $converter->convert($this->description)->getContent();
} }
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->useLogName('review')
->logAll()
->logOnlyDirty()
->dontLogEmptyChanges()
->setDescriptionForEvent(fn(string $eventName) => "Review {$eventName}");
}
} }

View File

@@ -4,6 +4,8 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
use Spatie\Activitylog\Support\LogOptions;
/** /**
* @property int $id * @property int $id
@@ -31,6 +33,9 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
*/ */
class Game extends Model class Game extends Model
{ {
use LogsActivity;
/** /**
* @var string[] * @var string[]
*/ */
@@ -50,4 +55,14 @@ class Game extends Model
{ {
return $this->hasMany(Entry::class); return $this->hasMany(Entry::class);
} }
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->useLogName('game')
->logAll()
->logOnlyDirty()
->dontLogEmptyChanges()
->setDescriptionForEvent(fn(string $eventName) => "Game {$eventName}");
}
} }

View File

@@ -3,6 +3,7 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
/** /**
* @property int $id * @property int $id
@@ -23,4 +24,9 @@ use Illuminate\Database\Eloquent\Model;
class Level extends Model class Level extends Model
{ {
protected $fillable = ['name', 'slug']; protected $fillable = ['name', 'slug'];
public function entries(): HasMany
{
return $this->hasMany(Entry::class);
}
} }

View File

@@ -3,6 +3,8 @@
namespace App\Models; namespace App\Models;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
/** /**
* @property int $id * @property int $id
@@ -23,4 +25,9 @@ use Illuminate\Database\Eloquent\Model;
class Modification extends Model class Modification extends Model
{ {
protected $fillable = [ 'name', 'slug' ]; protected $fillable = [ 'name', 'slug' ];
public function entries(): BelongsToMany
{
return $this->belongsToMany(Entry::class, 'entry_modifications');
}
} }

View File

@@ -10,6 +10,8 @@ use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use League\CommonMark\GithubFlavoredMarkdownConverter; use League\CommonMark\GithubFlavoredMarkdownConverter;
use Spatie\Activitylog\Models\Concerns\LogsActivity;
use Spatie\Activitylog\Support\LogOptions;
/** /**
* @property int $id * @property int $id
@@ -61,7 +63,7 @@ use League\CommonMark\GithubFlavoredMarkdownConverter;
class News extends Model class News extends Model
{ {
use SoftDeletes, HasGallery, HasXenforoUserId; use SoftDeletes, HasGallery, HasXenforoUserId, LogsActivity;
protected $table = 'news'; protected $table = 'news';
@@ -127,4 +129,14 @@ class News extends Model
return EntryHelpers::getYoutubeVideoId( $this->youtube_link ); return EntryHelpers::getYoutubeVideoId( $this->youtube_link );
} }
public function getActivitylogOptions(): LogOptions
{
return LogOptions::defaults()
->useLogName('news')
->logAll()
->logOnlyDirty()
->dontLogEmptyChanges()
->setDescriptionForEvent(fn(string $eventName) => "News {$eventName}");
}
} }

View File

@@ -159,7 +159,7 @@ class EntryPolicy
return $user->_can('romhackplaza', 'canModerateEntries' ); return $user->_can('romhackplaza', 'canModerateEntries' );
} }
public function moderate(User $user, Entry $entry): bool public function moderate(User $user, ?Entry $entry = null): bool
{ {
return $user->_can('romhackplaza', 'canModerateEntries' ); return $user->_can('romhackplaza', 'canModerateEntries' );
} }

View File

@@ -2,6 +2,7 @@
namespace App\Services; namespace App\Services;
use App\Helpers\EntryHelpers;
use App\Models\Entry; use App\Models\Entry;
use App\Models\EntryReview; use App\Models\EntryReview;
use App\Models\News; use App\Models\News;
@@ -65,7 +66,7 @@ class ActivityService
'user_id' => $entry->user_id, 'user_id' => $entry->user_id,
'badge' => EntryCard::ENTRY_TYPES_BADGE[$entry->type], 'badge' => EntryCard::ENTRY_TYPES_BADGE[$entry->type],
'badge_class' => $entry->type, 'badge_class' => $entry->type,
'excerpt' => $entry->description ? \Str::limit(strip_tags($entry->description), 80) : null, 'excerpt' => $entry->description ? \Str::limit(EntryHelpers::stripMarkdown(strip_tags($entry->description)), 80) : null,
'meta' => $entry->getRealPlatform()?->name 'meta' => $entry->getRealPlatform()?->name
]; ];
} }
@@ -82,7 +83,7 @@ class ActivityService
'user_id' => $news->user_id, 'user_id' => $news->user_id,
'badge' => 'News', 'badge' => 'News',
'badge_class' => 'news', 'badge_class' => 'news',
'excerpt' => $news->description ? \Str::limit(strip_tags($news->description), 80) : null, 'excerpt' => $news->description ? \Str::limit(EntryHelpers::stripMarkdown(strip_tags($news->description)), 80) : null,
'meta' => $news->category?->name 'meta' => $news->category?->name
]; ];
} }
@@ -99,7 +100,7 @@ class ActivityService
'user_id' => $message->user_id, 'user_id' => $message->user_id,
'badge' => 'Post', 'badge' => 'Post',
'badge_class' => 'message', 'badge_class' => 'message',
'excerpt' => $message->message ? \Str::limit(strip_tags($message->message), 80) : null, 'excerpt' => $message->message ? \Str::limit(EntryHelpers::stripBbCode(strip_tags($message->message)), 80) : null,
'meta' => null 'meta' => null
]; ];
@@ -117,7 +118,7 @@ class ActivityService
'user_id' => $thread->user_id, 'user_id' => $thread->user_id,
'badge' => 'Thread', 'badge' => 'Thread',
'badge_class' => 'thread', 'badge_class' => 'thread',
'excerpt' => $thread->message ? \Str::limit(strip_tags($thread->message), 80) : null, 'excerpt' => $thread->message ? \Str::limit(EntryHelpers::stripBbCode(strip_tags($thread->message)), 80) : null,
'meta' => null 'meta' => null
]; ];
@@ -152,7 +153,7 @@ class ActivityService
'user_id' => $review->user_id, 'user_id' => $review->user_id,
'badge' => 'Review', 'badge' => 'Review',
'badge_class' => 'review', 'badge_class' => 'review',
'excerpt' => $review->description ? \Str::limit(strip_tags($review->description), 80) : null, 'excerpt' => $review->description ? \Str::limit(EntryHelpers::stripMarkdown(strip_tags($review->description)), 80) : null,
'meta' => $review->entry()->exists() ? ( $review->entry->complete_title ?? $review->entry->title ) : null, 'meta' => $review->entry()->exists() ? ( $review->entry->complete_title ?? $review->entry->title ) : null,
]; ];
} }
@@ -199,6 +200,9 @@ class ActivityService
'post.user_id', 'post.message' 'post.user_id', 'post.message'
]) ])
->get() ->get()
->reject(function($post){
return (int) $post->user_id === config('xenforo.bot_user_id');
})
->map($this->formatMessage(...)) ->map($this->formatMessage(...))
->toArray(); ->toArray();
}); });
@@ -213,6 +217,7 @@ class ActivityService
->join('post', 'thread.first_post_id', '=', 'post.post_id') ->join('post', 'thread.first_post_id', '=', 'post.post_id')
->where('thread.discussion_state', 'visible') ->where('thread.discussion_state', 'visible')
->where('thread.discussion_type', '!=', 'redirect' ) ->where('thread.discussion_type', '!=', 'redirect' )
->where('thread.node_id', '!=', config('xenforo.comments_node_id') )
->orderByDesc('thread.post_date') ->orderByDesc('thread.post_date')
->limit(self::ITEMS_PER_TYPE) ->limit(self::ITEMS_PER_TYPE)
->select([ ->select([

View File

@@ -3,6 +3,7 @@
namespace App\Services; namespace App\Services;
use App\Models\Entry; use App\Models\Entry;
use App\Models\News;
use Illuminate\Http\Client\ConnectionException; use Illuminate\Http\Client\ConnectionException;
use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Http;
@@ -24,7 +25,7 @@ class XenforoApiService {
*/ */
private function get(string $endpoint, ?int $customUserId = null ): mixed private function get(string $endpoint, ?int $customUserId = null ): mixed
{ {
$response = Http::timeout(30)->withOptions(['verify' => false])->withHeaders([ $response = Http::timeout(8)->connectTimeout(4)->withOptions(['verify' => false])->withHeaders([
'XF-Api-Key' => $this->apiKey, 'XF-Api-Key' => $this->apiKey,
'XF-Api-User' => $customUserId ?? $this->superUserId, 'XF-Api-User' => $customUserId ?? $this->superUserId,
])->get("{$this->apiUrl}/{$endpoint}"); ])->get("{$this->apiUrl}/{$endpoint}");
@@ -39,7 +40,7 @@ class XenforoApiService {
private function post(string $endpoint, ?int $customUserId = null, array $data = [] ): mixed private function post(string $endpoint, ?int $customUserId = null, array $data = [] ): mixed
{ {
$response = Http::timeout(30)->withOptions(['verify' => false])->withHeaders([ $response = Http::timeout(8)->connectTimeout(4)->withOptions(['verify' => false])->withHeaders([
'XF-Api-Key' => $this->apiKey, 'XF-Api-Key' => $this->apiKey,
'XF-Api-User' => $customUserId ?? $this->superUserId, 'XF-Api-User' => $customUserId ?? $this->superUserId,
])->post("{$this->apiUrl}/{$endpoint}", $data); ])->post("{$this->apiUrl}/{$endpoint}", $data);
@@ -54,7 +55,7 @@ class XenforoApiService {
private function delete(string $endpoint, ?int $customUserId = null, array $data = [] ): mixed private function delete(string $endpoint, ?int $customUserId = null, array $data = [] ): mixed
{ {
$response = Http::timeout(30)->withOptions(['verify' => false])->withHeaders([ $response = Http::timeout(8)->connectTimeout(4)->withOptions(['verify' => false])->withHeaders([
'XF-Api-Key' => $this->apiKey, 'XF-Api-Key' => $this->apiKey,
'XF-Api-User' => $customUserId ?? $this->superUserId, 'XF-Api-User' => $customUserId ?? $this->superUserId,
])->delete("{$this->apiUrl}/{$endpoint}", $data); ])->delete("{$this->apiUrl}/{$endpoint}", $data);
@@ -74,20 +75,40 @@ class XenforoApiService {
} }
return Cache::remember("xf_alerts_{$userId}", 60, function() use($userId) { return Cache::remember("xf_alerts_{$userId}", 60, function() use($userId) {
return $this->get("alerts?page=1&cutoff=7days", $userId); $result = $this->get("alerts?page=1&cutoff=7days", $userId);
if (is_array($result)) {
if (isset($result['alerts'])) {
$result['alerts'] = collect($result['alerts'])->take(8)->all();
} else {
$result = collect($result)->take(8)->all();
}
}
return $result;
}); });
} }
public function markAllNotificationsRead(int $userId): void public function markAllNotificationsRead(int $userId): void
{ {
Cache::forget("xf_alerts_{$userId}"); Cache::forget("xf_alerts_{$userId}");
$this->post("alerts/mark-all", $userId ); $this->post("alerts/mark-all", $userId, ['read' => true, 'viewed' => true] );
} }
public function getConversations(int $userId): mixed public function getConversations(int $userId): mixed
{ {
return Cache::remember("xf_conversations_{$userId}", 60, function() use($userId) { return Cache::remember("xf_conversations_{$userId}", 60, function() use($userId) {
return $this->get("conversations?page=1&receiver_id={$userId}", $userId); $result = $this->get("conversations?page=1&receiver_id={$userId}", $userId);
if (is_array($result)) {
if (isset($result['conversations'])) {
$result['conversations'] = collect($result['conversations'])->take(8)->all();
} else {
$result = collect($result)->take(8)->all();
}
}
return $result;
}); });
} }
@@ -104,10 +125,10 @@ class XenforoApiService {
if( !$entry->comments_thread_id || $entry->comments_thread_id <= 0 ){ if( !$entry->comments_thread_id || $entry->comments_thread_id <= 0 ){
$data = [ $data = [
'node_id' => config('xenforo.comments_node_id'), 'node_id' => config('xenforo.comments_node_id'),
'title' => $entry->complete_title, 'title' => $entry->complete_title ?? $entry->title,
'message' => $entry->description, 'message' => $entry->description,
'prefix_id' => config('xenforo.comments_prefixes')[$entry->type] ?? 1, 'prefix_id' => config('xenforo.comments_prefixes')[$entry->type ?? "news"] ?? 1,
'custom_fields' => [ 'entry_id' => $entry->id ], 'custom_fields' => $entry->type ? [ 'entry_id' => $entry->id ] : [ 'news_id' => $entry->id ],
'discussion_open' => true, 'discussion_open' => true,
]; ];

View File

@@ -14,6 +14,10 @@ class ErrorBlock extends Component
'icon' => 'ban', 'icon' => 'ban',
'message' => '%s' 'message' => '%s'
], ],
'not-found' => [
'icon' => 'sticky-note-off',
'message' => "404: This page does not exist."
],
'page-not-allowed' => [ 'page-not-allowed' => [
'icon' => 'shield-ban', 'icon' => 'shield-ban',
'message' => "You do not have permission to access this page.\nRequired permission: %s" 'message' => "You do not have permission to access this page.\nRequired permission: %s"
@@ -21,6 +25,10 @@ class ErrorBlock extends Component
'user-state-not-valid' => [ 'user-state-not-valid' => [
'icon' => 'shield-ban', 'icon' => 'shield-ban',
'message' => "You do not have permission to access this page.\nYour user profile is incomplete: %s\nGo back to the forum for more details." 'message' => "You do not have permission to access this page.\nYour user profile is incomplete: %s\nGo back to the forum for more details."
],
'server-error' => [
'icon' => 'bomb',
'message' => "500: Internal Server Error."
] ]
]; ];

View File

@@ -61,6 +61,10 @@ class SubmitEntryStatus extends Component
} }
} }
if( $this->isEdit && $this->entry->state === 'pending' && isset( $states['published'] ) ) {
unset( $states['published'] );
}
return $states; return $states;
} }

View File

@@ -16,6 +16,7 @@ return Application::configure(basePath: dirname(__DIR__))
$middleware->encryptCookies(except: ['xf_session','xf_user','xf_csrf','xf_style_variation','activity_filters']); $middleware->encryptCookies(except: ['xf_session','xf_user','xf_csrf','xf_style_variation','activity_filters']);
$middleware->alias([ $middleware->alias([
'xf.auth' => \App\Http\Middleware\CheckXenForoPermissions::class, 'xf.auth' => \App\Http\Middleware\CheckXenForoPermissions::class,
'submissions.maintenance' => \App\Http\Middleware\SubmissionsEnabled::class,
]); ]);
$middleware->redirectGuestsTo(function(Request $request): void { $middleware->redirectGuestsTo(function(Request $request): void {
if( $request->is('manage*')) if( $request->is('manage*'))
@@ -24,5 +25,28 @@ return Application::configure(basePath: dirname(__DIR__))
$middleware->append(\App\Http\Middleware\CheckXenForoUserState::class); $middleware->append(\App\Http\Middleware\CheckXenForoUserState::class);
}) })
->withExceptions(function (Exceptions $exceptions): void { ->withExceptions(function (Exceptions $exceptions): void {
// $exceptions->render(function(Throwable $e, $request) {
if (
$e instanceof \Illuminate\Validation\ValidationException ||
$e instanceof \Illuminate\Auth\AuthenticationException ||
$e instanceof \Illuminate\Auth\Access\AuthorizationException ||
$e instanceof \Symfony\Component\HttpKernel\Exception\HttpException
) {
return null;
}
$statusCode = method_exists($e, 'getStatusCode') ? $e->getStatusCode() : 500;
if( app()->environment('production') && $statusCode >= 500 && !$request->expectsJson() ){
$errorId = (string) Str::uuid();
\Illuminate\Support\Facades\Log::error($e->getMessage(), [
'error_id' => $errorId,
'exception' => $e,
'url' => $request->fullUrl(),
'user_id' => (optional($request->user())->user_id ?? 0),
]);
return response()->view('errors.500', ['errorId' => $errorId], 500 );
}
});
})->create(); })->create();

View File

@@ -1,18 +0,0 @@
<?php
return [
'bot_user_id' => 1,
'comments_node_id' => 4,
'comments_prefixes' => [
'translations' => 1,
'romhacks' => 2,
'homebrew' => 3,
'utilities' => 4,
'documents' => 5,
'lua-scripts' => 6,
'tutorials' => 7,
'news' => 8
],
];

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('entries', function (Blueprint $table) {
$table->unsignedBigInteger('old_download_count')->default(0);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('entries', function (Blueprint $table) {
$table->dropColumn('old_download_count');
});
}
};

View File

@@ -10,7 +10,8 @@ body {
background-color: var(--bg); background-color: var(--bg);
color: var(--text); color: var(--text);
display: flex; display: flex;
height: 100vh; height: 100dvh;
min-height: 100dvh;
overflow: hidden; overflow: hidden;
} }
@@ -136,6 +137,7 @@ ul {
border: 1px solid var(--border); border: 1px solid var(--border);
display: flex; display: flex;
min-width: 0; min-width: 0;
max-width: 100%;
flex-direction: column; flex-direction: column;
transition: transform 0.2s, border-color 0.2s; transition: transform 0.2s, border-color 0.2s;
cursor: pointer; cursor: pointer;
@@ -178,6 +180,7 @@ ul {
.\$entry-card-info { .\$entry-card-info {
padding: 15px; padding: 15px;
flex-grow: 1; flex-grow: 1;
min-width: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@@ -188,13 +191,16 @@ ul {
font-size: 1.1rem; font-size: 1.1rem;
margin-bottom: 5px; margin-bottom: 5px;
line-height: 1.3; line-height: 1.3;
word-wrap: break-word; overflow-wrap: anywhere;
word-break: break-word;
} }
.\$entry-card-author { .\$entry-card-author {
color: var(--rhpz-orange); color: var(--rhpz-orange);
font-size: 0.85rem; font-size: 0.85rem;
margin-bottom: 10px; margin-bottom: 10px;
overflow-wrap: anywhere;
word-break: break-word;
} }
.\$entry-card-meta { .\$entry-card-meta {
@@ -254,6 +260,9 @@ ul {
.\$entry-card-meta { .\$entry-card-meta {
font-size: 0.75rem; font-size: 0.75rem;
flex-direction: column;
align-items: flex-start;
gap: 6px;
} }
} }
@@ -334,6 +343,45 @@ ul {
background-color: rgba(129, 199, 132, 0.1); background-color: rgba(129, 199, 132, 0.1);
} }
.\$back-to-top {
display: none;
}
@media (max-width: 768px) {
.\$back-to-top {
display: none;
align-items: center;
justify-content: center;
gap: 8px;
position: fixed;
right: 14px;
bottom: 14px;
z-index: 1000;
padding: 10px 14px;
border-radius: 999px;
background-color: var(--bg2);
border: 1px solid var(--border);
color: var(--text);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
opacity: 0;
pointer-events: none;
transform: translateY(10px);
transition: opacity 0.2s ease, transform 0.2s ease;
}
.\$back-to-top.visible {
display: inline-flex;
opacity: 1;
pointer-events: auto;
transform: translateY(0);
}
.\$back-to-top span {
font-size: 0.78rem;
font-weight: 600;
}
}
/* BLOCK */ /* BLOCK */
.\$block { .\$block {
@@ -385,21 +433,36 @@ ul {
color: var(--text3); color: var(--text3);
border-color: var(--rhpz-orange); border-color: var(--rhpz-orange);
} }
.\$badge.blue, .\$badge.translations { .\$badge.blue, .\$badge.romhacks {
background-color: var(--info); background-color: var(--info);
color: var(--text); color: var(--text);
border-color: var(--info); border-color: var(--info);
} }
.\$badge.green, .\$badge.romhacks { .\$badge.green, .\$badge.translations {
background-color: var(--success2); background-color: var(--success2);
color: var(--text); color: var(--text);
border-color: var(--success2); border-color: var(--success2);
} }
.\$badge.red, .\$badge.homebrew {
background-color: #bf2323;
color: var(--text);
border-color: #bf2323;
}
.\$badge.yellow, .\$badge.utilities { .\$badge.yellow, .\$badge.utilities {
background-color: #fdeb0f; background-color: #fdeb0f;
color: #000; color: #000;
border-color: #fdeb0f; border-color: #fdeb0f;
} }
.\$badge.purple, .\$badge.documents{
background-color: #8b23bf;
color: var(--text);
border-color: #8b23bf;
}
.\$badge.brown, .\$badge.lua-scripts {
background-color: #b4825f;
color: var(--text);
border-color: #b4825f;
}
.\$topbar-badge { .\$topbar-badge {
position: absolute; position: absolute;
@@ -575,448 +638,6 @@ ul {
} }
/* File: resources/css/components/database.css */
.\$filter-bar {
display: flex;
gap: 15px;
background-color: var(--bg2);
padding: 15px;
border: 1px solid var(--border);
margin-bottom: 20px;
flex-wrap: wrap;
align-items: center;
.\$filter-bar-search {
flex: 1;
max-width: 400px;
background-color: var(--bg);
border: 1px solid var(--border);
display: flex;
align-items: center;
padding: 8px 12px;
gap: 8px;
}
}
.\$database-wrapper {
display: flex;
gap: 20px;
align-items: flex-start;
.\$database-filters {
width: 300px;
flex-shrink: 0;
display: flex;
flex-direction: column;
gap: 2px;
background-color: var(--bg2);
border: 1px solid var(--border);
.\$filter-group {
border-bottom: 1px solid var(--border);
overflow: hidden;
&:last-child {
border-bottom: none;
}
}
.\$filter-title-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 14px;
background-color: var(--bg3);
cursor: pointer;
user-select: none;
.\$filter-title {
font-size: 0.8rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.8px;
color: var(--text2);
margin: 0;
}
}
.\$filter-mode {
display: flex;
gap: 4px;
}
.\$filter-btn-mode {
background: none;
border: 1px solid var(--border);
color: var(--text2);
font-size: 0.7rem;
font-weight: 600;
padding: 2px 7px;
cursor: pointer;
font-family: var(--typography);
transition: all 0.15s;
letter-spacing: 0.5px;
&:hover {
border-color: var(--rhpz-orange);
color: var(--rhpz-orange);
}
&.\$active {
background-color: var(--rhpz-orange);
border-color: var(--rhpz-orange);
color: var(--text3);
}
}
.\$filter-options {
padding: 6px 0;
max-height: 180px;
overflow-y: auto;
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: var(--bg2);
}
&::-webkit-scrollbar-thumb {
background: var(--border);
}
}
.\$filter-option {
display: flex;
align-items: center;
gap: 9px;
padding: 6px 14px;
font-size: 0.88rem;
color: var(--text);
cursor: pointer;
transition: background-color 0.1s;
&:hover {
background-color: var(--bg4);
}
}
.\$filter-option input[type="checkbox"] {
accent-color: var(--rhpz-orange);
width: 14px;
height: 14px;
cursor: pointer;
flex-shrink: 0;
}
}
.\$database-results {
flex: 1;
min-width: 0;
.\$database-sort {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px solid var(--border);
flex-wrap: wrap;
.\$btn {
font-size: 0.85rem;
padding: 6px 12px;
&.\$active {
border-color: var(--rhpz-orange);
color: var(--rhpz-orange);
}
}
}
.\$database-results-count {
margin-left: auto;
font-size: 0.85rem;
color: var(--text2);
}
.\$database-active-filters {
display: flex;
flex-wrap: wrap;
gap: 6px;
margin-bottom: 15px;
}
.\$database-active-filter-tag {
display: inline-flex;
align-items: center;
gap: 6px;
background-color: var(--bg3);
border: 1px solid var(--border);
padding: 3px 10px;
font-size: 0.8rem;
color: var(--text);
.\$tag-type {
color: var(--rhpz-orange);
font-weight: 600;
font-size: 0.75rem;
text-transform: uppercase;
letter-spacing: 0.5px;
}
button {
background: none;
border: none;
color: var(--text2);
cursor: pointer;
padding: 0;
display: flex;
align-items: center;
transition: color 0.15s;
&:hover {
color: var(--text);
}
}
}
.\$database-empty {
grid-column: 1 / -1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 60px 20px;
color: var(--text2);
background-color: var(--bg2);
border: 1px solid var(--border);
gap: 15px;
text-align: center;
i {
color: var(--border);
}
p {
font-size: 0.95rem;
}
}
}
}
.\$database-pagination {
display: flex;
justify-content: center;
align-items: center;
gap: 4px;
margin-top: 20px;
.\$btn {
min-width: 36px;
padding: 6px 10px;
font-size: 0.85rem;
display: flex;
align-items: center;
justify-content: center;
}
.\$active {
background-color: var(--rhpz-orange);
border-color: var(--rhpz-orange);
color: #111;
font-weight: 600;
}
}
.\$database-search {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
@media (max-width: 900px) {
.\$database-layout {
flex-direction: column;
}
.\$database-wrapper {
flex-direction: column;
}
.\$database-filters {
width: 100%;
display: grid;
grid-template-columns: repeat(2,1fr);
}
.\$database-filter-group:last-child {
border-bottom: 1px solid var(--border);
}
.\$database-results-count {
margin-left: 0;
width: 100%;
}
}
@media (max-width: 768px) {
.\$database-search {
gap: 8px;
margin-bottom: 15px;
flex-wrap: wrap;
}
.\$database-wrapper {
flex-direction: column;
gap: 15px;
}
.\$database-filters {
width: 100%;
grid-template-columns: 1fr;
order: -1;
margin-bottom: 10px;
}
.\$database-filter-group {
padding: 12px 0;
}
.\$grid-entries {
grid-template-columns: repeat(3, 1fr);
gap: 15px;
}
}
@media (max-width: 600px) {
.\$database-search {
flex-direction: column;
}
.\$database-filters {
grid-template-columns: 1fr;
}
.\$grid-entries {
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.\$database-filter-group {
padding: 10px 0;
}
}
@media (max-width: 420px) {
.\$grid-entries {
grid-template-columns: 1fr;
gap: 10px;
}
.\$database-search input {
font-size: 0.85rem;
padding: 6px 8px;
}
}
.\$filter-chevron {
transition: transform 0.2s ease;
color: var(--text2);
flex-shrink: 0;
}
.\$filter-chevron.rotated {
transform: rotate(-90deg);
}
.\$internal-filter-search {
display: flex;
align-items: center;
gap: 7px;
padding: 7px 14px;
border-bottom: 1px solid var(--border);
background-color: var(--bg);
i {
color: var(--text2);
flex-shrink: 0;
}
input {
background: none;
border: none;
outline: none;
color: var(--text);
font-family: var(--typography);
font-size: 0.85rem;
width: 100%;
&::placeholder {
color: var(--text2);
}
}
}
.\$filter-title-left {
display: flex;
align-items: center;
gap: 7px;
}
.\$filter-title-right {
display: flex;
align-items: center;
gap: 6px;
}
.\$internal-filter-count {
display: inline-flex;
align-items: center;
justify-content: center;
background-color: var(--rhpz-orange);
color: #111;
font-size: 0.7rem;
font-weight: 700;
min-width: 18px;
height: 18px;
padding: 0 5px;
line-height: 1;
}
.\$internal-filter-clear {
background: none;
border: 1px solid var(--border);
color: var(--text2);
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
padding: 0;
transition: all 0.15s;
&:hover {
border-color: var(--error);
color: var(--error);
}
}
.\$filter-search-clear {
background: none;
border: none;
color: var(--text2);
cursor: pointer;
display: flex;
align-items: center;
padding: 0;
flex-shrink: 0;
transition: color 0.15s;
&:hover {
color: var(--text);
}
}
/* File: resources/css/components/drafts.css */ /* File: resources/css/components/drafts.css */
.\$drafts-count { .\$drafts-count {
font-size: 0.85rem; font-size: 0.85rem;
@@ -1797,6 +1418,37 @@ ul {
min-width: 20px; min-width: 20px;
text-align: center; text-align: center;
} }
.\$gallery-mobile-controls {
display: none;
}
@media (max-width: 1024px) {
.\$gallery-mobile-controls {
display: flex;
justify-content: flex-end;
gap: 6px;
margin-top: 6px;
}
.\$gallery-move-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: 1px solid var(--border);
background-color: var(--bg2);
color: var(--text);
cursor: pointer;
padding: 0;
}
.\$gallery-move-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
.\$authors-list { .\$authors-list {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
@@ -1914,13 +1566,30 @@ ul {
} }
@media (max-width: 600px) { @media (max-width: 600px) {
.\$upload-item-actions { .\$upload-item {
flex-direction: column; flex-direction: column;
align-items: stretch;
}
.\$upload-item-info {
width: 100%;
flex: 1 1 auto;
min-width: 0;
}
.\$upload-item-actions {
flex-direction: row;
justify-content: flex-end;
align-items: center;
gap: 8px; gap: 8px;
width: 100%;
margin-top: 8px;
flex-basis: 100%;
} }
.\$upload-item-actions .\$btn { .\$upload-item-actions .\$btn {
width: 100%; width: auto;
flex: 0 0 auto;
} }
} }
.file-state-icon { width: 18px; height: 18px; } .file-state-icon { width: 18px; height: 18px; }
@@ -2077,10 +1746,18 @@ ul {
.\$grid-hashes { .\$grid-hashes {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.\$grid-credits {
grid-template-columns: 1fr;
}
.\$grid-first {
display: none;
}
.\$hash-first { .\$hash-first {
display: none; display: none;
} }
.\$author-search-selected {
display: none;
}
} }
@media (max-width: 600px) { @media (max-width: 600px) {
@@ -2110,6 +1787,22 @@ ul {
.\$form-error-text { .\$form-error-text {
font-size: 0.8rem; font-size: 0.8rem;
} }
.\$form-type-of-checkboxes {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
label {
box-sizing: border-box;
margin: 0;
}
input[type="checkbox"] {
transform: scale(1.5);
margin-right: 1.33rem;
}
}
} }
@@ -2146,11 +1839,41 @@ ul {
.\$grid-entries { .\$grid-entries {
display: grid; display: grid;
grid-template-columns: repeat(6,1fr); grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 20px; gap: 20px;
margin-bottom: 20px; margin-bottom: 20px;
} }
@media (max-width: 1400px) {
.\$grid-entries {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
}
@media (max-width: 1200px) {
.\$grid-entries {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 900px) {
.\$grid-entries {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 640px) {
.\$grid-entries {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 420px) {
.\$grid-entries {
grid-template-columns: 1fr;
}
}
/* File: resources/css/components/hovercard.css */ /* File: resources/css/components/hovercard.css */
@@ -4024,7 +3747,7 @@ ul {
.\$activity-tl-card-description { .\$activity-tl-card-description {
font-size: 0.8rem; font-size: 0.8rem;
color: var(--text2); color: var(--text2);
white-space: nowrap; word-break: break-word;
text-overflow: ellipsis; text-overflow: ellipsis;
line-height: 1.3; line-height: 1.3;
} }
@@ -4066,6 +3789,8 @@ ul {
@media (max-width: 600px) { @media (max-width: 600px) {
.activity-tl-header { flex-direction: column; align-items: flex-start; } .activity-tl-header { flex-direction: column; align-items: flex-start; }
.activity-tl-thumb { display: none; } .activity-tl-thumb { display: none; }
.activity-tl-left { display: none; }
.activity-tl-card-description { display: none; }
.activity-day-sep { padding-left: 44px; } .activity-day-sep { padding-left: 44px; }
.activity-tl-left { width: 44px; } .activity-tl-left { width: 44px; }
@@ -4080,7 +3805,7 @@ ul {
@media (max-width: 768px) { @media (max-width: 768px) {
.\$activity-timeline { .\$activity-timeline {
padding-left: 50px; padding-left: 0px;
} }
.\$activity-tl-left { .\$activity-tl-left {
@@ -4362,6 +4087,7 @@ ul {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
min-height: 0;
} }
#topbar { #topbar {
@@ -4674,6 +4400,30 @@ ul {
line-height: 1.6; line-height: 1.6;
color: var(--text); color: var(--text);
margin-bottom: 30px; margin-bottom: 30px;
word-break: break-word;
p {
margin: 0 0 14px;
&:last-child {
margin-bottom: 0;
}
}
pre {
max-width: 100%;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-word;
padding: 12px;
border-radius: 6px;
margin: 0 0 14px;
}
code {
white-space: pre-wrap;
word-break: break-word;
}
} }
.\$entry-gallery { .\$entry-gallery {
@@ -4793,6 +4543,9 @@ ul {
overflow: hidden; overflow: hidden;
background-color: var(--bg4); background-color: var(--bg4);
border: 1px solid var(--border); border: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: center;
img { img {
width: 100%; width: 100%;
@@ -5175,6 +4928,7 @@ ul {
flex-shrink: 0; flex-shrink: 0;
transition: transform 0.3s ease; transition: transform 0.3s ease;
z-index: 100; z-index: 100;
overflow: hidden;
.\$menu-header { .\$menu-header {
padding: 10px; padding: 10px;
@@ -5212,9 +4966,12 @@ ul {
} }
.\$menu-navigation { .\$menu-navigation {
flex-grow: 1; flex: 1 1 auto;
min-height: 0;
padding: 10px 0; padding: 10px 0;
overflow-y: auto; overflow-y: auto;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
.\$menu-group { .\$menu-group {
margin-bottom: 20px; margin-bottom: 20px;
@@ -5264,7 +5021,9 @@ ul {
} }
.\$menu-user { .\$menu-user {
padding: 15px 20px; flex-shrink: 0;
margin-top: auto;
padding: 15px 20px calc(15px + env(safe-area-inset-bottom)) 20px;
border-top: 1px solid var(--border); border-top: 1px solid var(--border);
display: flex; display: flex;
align-items: center; align-items: center;
@@ -5299,6 +5058,7 @@ ul {
&.\$username { &.\$username {
font-size: 0.9rem; font-size: 0.9rem;
font-weight: 600; font-weight: 600;
text-decoration: none !important;
} }
&.\$user_role { &.\$user_role {
font-size: 0.75rem; font-size: 0.75rem;
@@ -5927,7 +5687,9 @@ ul {
position: fixed; position: fixed;
left: 0; left: 0;
top: 60px; top: 60px;
height: calc(100vh - 60px); height: calc(100dvh - 60px);
max-height: calc(100dvh - 60px);
min-height: 0;
transform: translateX(-100%); transform: translateX(-100%);
transition: transform 0.3s ease-in-out; transition: transform 0.3s ease-in-out;
z-index: 999; z-index: 999;

View File

@@ -9,7 +9,8 @@ body {
background-color: var(--bg); background-color: var(--bg);
color: var(--text); color: var(--text);
display: flex; display: flex;
height: 100vh; height: 100dvh;
min-height: 100dvh;
overflow: hidden; overflow: hidden;
} }

View File

@@ -30,6 +30,7 @@
border: 1px solid var(--border); border: 1px solid var(--border);
display: flex; display: flex;
min-width: 0; min-width: 0;
max-width: 100%;
flex-direction: column; flex-direction: column;
transition: transform 0.2s, border-color 0.2s; transition: transform 0.2s, border-color 0.2s;
cursor: pointer; cursor: pointer;
@@ -72,6 +73,7 @@
.entry-card-info { .entry-card-info {
padding: 15px; padding: 15px;
flex-grow: 1; flex-grow: 1;
min-width: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
} }
@@ -82,13 +84,16 @@
font-size: 1.1rem; font-size: 1.1rem;
margin-bottom: 5px; margin-bottom: 5px;
line-height: 1.3; line-height: 1.3;
word-wrap: break-word; overflow-wrap: anywhere;
word-break: break-word;
} }
.entry-card-author { .entry-card-author {
color: var(--rhpz-orange); color: var(--rhpz-orange);
font-size: 0.85rem; font-size: 0.85rem;
margin-bottom: 10px; margin-bottom: 10px;
overflow-wrap: anywhere;
word-break: break-word;
} }
.entry-card-meta { .entry-card-meta {
@@ -148,6 +153,9 @@
.entry-card-meta { .entry-card-meta {
font-size: 0.75rem; font-size: 0.75rem;
flex-direction: column;
align-items: flex-start;
gap: 6px;
} }
} }

View File

@@ -43,6 +43,45 @@
background-color: rgba(129, 199, 132, 0.1); background-color: rgba(129, 199, 132, 0.1);
} }
.back-to-top {
display: none;
}
@media (max-width: 768px) {
.back-to-top {
display: none;
align-items: center;
justify-content: center;
gap: 8px;
position: fixed;
right: 14px;
bottom: 14px;
z-index: 1000;
padding: 10px 14px;
border-radius: 999px;
background-color: var(--bg2);
border: 1px solid var(--border);
color: var(--text);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
opacity: 0;
pointer-events: none;
transform: translateY(10px);
transition: opacity 0.2s ease, transform 0.2s ease;
}
.back-to-top.visible {
display: inline-flex;
opacity: 1;
pointer-events: auto;
transform: translateY(0);
}
.back-to-top span {
font-size: 0.78rem;
font-weight: 600;
}
}
/* BLOCK */ /* BLOCK */
.block { .block {
@@ -94,21 +133,36 @@
color: var(--text3); color: var(--text3);
border-color: var(--rhpz-orange); border-color: var(--rhpz-orange);
} }
.badge.blue, .badge.translations { .badge.blue, .badge.romhacks {
background-color: var(--info); background-color: var(--info);
color: var(--text); color: var(--text);
border-color: var(--info); border-color: var(--info);
} }
.badge.green, .badge.romhacks { .badge.green, .badge.translations {
background-color: var(--success2); background-color: var(--success2);
color: var(--text); color: var(--text);
border-color: var(--success2); border-color: var(--success2);
} }
.badge.red, .badge.homebrew {
background-color: #bf2323;
color: var(--text);
border-color: #bf2323;
}
.badge.yellow, .badge.utilities { .badge.yellow, .badge.utilities {
background-color: #fdeb0f; background-color: #fdeb0f;
color: #000; color: #000;
border-color: #fdeb0f; border-color: #fdeb0f;
} }
.badge.purple, .badge.documents{
background-color: #8b23bf;
color: var(--text);
border-color: #8b23bf;
}
.badge.brown, .badge.lua-scripts {
background-color: #b4825f;
color: var(--text);
border-color: #b4825f;
}
.topbar-badge { .topbar-badge {
position: absolute; position: absolute;

View File

@@ -227,8 +227,10 @@
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
flex-wrap: wrap;
gap: 4px; gap: 4px;
margin-top: 20px; margin-top: 20px;
width: 100%;
.btn { .btn {
min-width: 36px; min-width: 36px;
@@ -255,18 +257,20 @@
} }
@media (max-width: 900px) { @media (max-width: 900px) {
.database-layout {
flex-direction: column;
}
.database-wrapper { .database-wrapper {
flex-direction: column; flex-direction: column;
width: 100%;
} }
.database-filters { .database-filters {
width: 100%; width: 100%;
display: grid; display: grid;
grid-template-columns: repeat(2,1fr); grid-template-columns: repeat(2, minmax(0, 1fr));
}
.database-results {
width: 100%;
min-width: 0;
} }
.database-filter-group:last-child { .database-filter-group:last-child {
@@ -286,13 +290,32 @@
flex-wrap: wrap; flex-wrap: wrap;
} }
.filter-bar {
flex-direction: column;
align-items: stretch;
}
.filter-bar .filter-bar-search {
max-width: none;
flex: initial;
width: 100%;
}
.filter-bar .btn {
width: 100%;
justify-content: center;
}
.database-wrapper { .database-wrapper {
flex-direction: column; flex-direction: column;
gap: 15px; gap: 15px;
width: 100%;
} }
.database-filters { .database-filters {
width: 100%; width: min(100%, 420px);
max-width: 420px;
margin: 0 auto;
grid-template-columns: 1fr; grid-template-columns: 1fr;
order: -1; order: -1;
margin-bottom: 10px; margin-bottom: 10px;
@@ -303,7 +326,7 @@
} }
.grid-entries { .grid-entries {
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 15px; gap: 15px;
} }
} }
@@ -311,6 +334,20 @@
@media (max-width: 600px) { @media (max-width: 600px) {
.database-search { .database-search {
flex-direction: column; flex-direction: column;
align-items: stretch;
}
.filter-bar {
padding: 12px;
}
.filter-bar .filter-bar-search {
max-width: none;
}
.filter-bar .btn {
width: 100%;
justify-content: center;
} }
.database-filters { .database-filters {
@@ -318,7 +355,7 @@
} }
.grid-entries { .grid-entries {
grid-template-columns: repeat(2, 1fr); grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 12px; gap: 12px;
} }

View File

@@ -393,6 +393,37 @@
min-width: 20px; min-width: 20px;
text-align: center; text-align: center;
} }
.gallery-mobile-controls {
display: none;
}
@media (max-width: 1024px) {
.gallery-mobile-controls {
display: flex;
justify-content: flex-end;
gap: 6px;
margin-top: 6px;
}
.gallery-move-btn {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border: 1px solid var(--border);
background-color: var(--bg2);
color: var(--text);
cursor: pointer;
padding: 0;
}
.gallery-move-btn:disabled {
opacity: 0.4;
cursor: not-allowed;
}
}
.authors-list { .authors-list {
display: grid; display: grid;
grid-template-columns: repeat(4, 1fr); grid-template-columns: repeat(4, 1fr);
@@ -510,13 +541,30 @@
} }
@media (max-width: 600px) { @media (max-width: 600px) {
.upload-item-actions { .upload-item {
flex-direction: column; flex-direction: column;
align-items: stretch;
}
.upload-item-info {
width: 100%;
flex: 1 1 auto;
min-width: 0;
}
.upload-item-actions {
flex-direction: row;
justify-content: flex-end;
align-items: center;
gap: 8px; gap: 8px;
width: 100%;
margin-top: 8px;
flex-basis: 100%;
} }
.upload-item-actions .btn { .upload-item-actions .btn {
width: 100%; width: auto;
flex: 0 0 auto;
} }
} }
.file-state-icon { width: 18px; height: 18px; } .file-state-icon { width: 18px; height: 18px; }
@@ -673,10 +721,18 @@
.grid-hashes { .grid-hashes {
grid-template-columns: 1fr; grid-template-columns: 1fr;
} }
.grid-credits {
grid-template-columns: 1fr;
}
.grid-first {
display: none;
}
.hash-first { .hash-first {
display: none; display: none;
} }
.author-search-selected {
display: none;
}
} }
@media (max-width: 600px) { @media (max-width: 600px) {
@@ -706,4 +762,20 @@
.form-error-text { .form-error-text {
font-size: 0.8rem; font-size: 0.8rem;
} }
.form-type-of-checkboxes {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 1rem;
label {
box-sizing: border-box;
margin: 0;
}
input[type="checkbox"] {
transform: scale(1.5);
margin-right: 1.33rem;
}
}
} }

View File

@@ -30,8 +30,38 @@
.grid-entries { .grid-entries {
display: grid; display: grid;
grid-template-columns: repeat(6,1fr); grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 20px; gap: 20px;
margin-bottom: 20px; margin-bottom: 20px;
} }
@media (max-width: 1400px) {
.grid-entries {
grid-template-columns: repeat(5, minmax(0, 1fr));
}
}
@media (max-width: 1200px) {
.grid-entries {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 900px) {
.grid-entries {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
}
@media (max-width: 640px) {
.grid-entries {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
}
@media (max-width: 420px) {
.grid-entries {
grid-template-columns: 1fr;
}
}

View File

@@ -223,7 +223,7 @@
.activity-tl-card-description { .activity-tl-card-description {
font-size: 0.8rem; font-size: 0.8rem;
color: var(--text2); color: var(--text2);
white-space: nowrap; word-break: break-word;
text-overflow: ellipsis; text-overflow: ellipsis;
line-height: 1.3; line-height: 1.3;
} }
@@ -265,6 +265,8 @@
@media (max-width: 600px) { @media (max-width: 600px) {
.activity-tl-header { flex-direction: column; align-items: flex-start; } .activity-tl-header { flex-direction: column; align-items: flex-start; }
.activity-tl-thumb { display: none; } .activity-tl-thumb { display: none; }
.activity-tl-left { display: none; }
.activity-tl-card-description { display: none; }
.activity-day-sep { padding-left: 44px; } .activity-day-sep { padding-left: 44px; }
.activity-tl-left { width: 44px; } .activity-tl-left { width: 44px; }
@@ -279,7 +281,7 @@
@media (max-width: 768px) { @media (max-width: 768px) {
.activity-timeline { .activity-timeline {
padding-left: 50px; padding-left: 0px;
} }
.activity-tl-left { .activity-tl-left {

View File

@@ -3,6 +3,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
min-height: 0;
} }
#topbar { #topbar {

View File

@@ -101,6 +101,30 @@
line-height: 1.6; line-height: 1.6;
color: var(--text); color: var(--text);
margin-bottom: 30px; margin-bottom: 30px;
word-break: break-word;
p {
margin: 0 0 14px;
&:last-child {
margin-bottom: 0;
}
}
pre {
max-width: 100%;
overflow-x: auto;
white-space: pre-wrap;
word-break: break-word;
padding: 12px;
border-radius: 6px;
margin: 0 0 14px;
}
code {
white-space: pre-wrap;
word-break: break-word;
}
} }
.entry-gallery { .entry-gallery {
@@ -220,6 +244,9 @@
overflow: hidden; overflow: hidden;
background-color: var(--bg4); background-color: var(--bg4);
border: 1px solid var(--border); border: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: center;
img { img {
width: 100%; width: 100%;

View File

@@ -7,6 +7,7 @@
flex-shrink: 0; flex-shrink: 0;
transition: transform 0.3s ease; transition: transform 0.3s ease;
z-index: 100; z-index: 100;
overflow: hidden;
.menu-header { .menu-header {
padding: 10px; padding: 10px;
@@ -44,9 +45,12 @@
} }
.menu-navigation { .menu-navigation {
flex-grow: 1; flex: 1 1 auto;
min-height: 0;
padding: 10px 0; padding: 10px 0;
overflow-y: auto; overflow-y: auto;
-webkit-overflow-scrolling: touch;
overscroll-behavior: contain;
.menu-group { .menu-group {
margin-bottom: 20px; margin-bottom: 20px;
@@ -96,7 +100,9 @@
} }
.menu-user { .menu-user {
padding: 15px 20px; flex-shrink: 0;
margin-top: auto;
padding: 15px 20px calc(15px + env(safe-area-inset-bottom)) 20px;
border-top: 1px solid var(--border); border-top: 1px solid var(--border);
display: flex; display: flex;
align-items: center; align-items: center;
@@ -131,6 +137,7 @@
&.username { &.username {
font-size: 0.9rem; font-size: 0.9rem;
font-weight: 600; font-weight: 600;
text-decoration: none !important;
} }
&.user_role { &.user_role {
font-size: 0.75rem; font-size: 0.75rem;

View File

@@ -8,7 +8,9 @@
position: fixed; position: fixed;
left: 0; left: 0;
top: 60px; top: 60px;
height: calc(100vh - 60px); height: calc(100dvh - 60px);
max-height: calc(100dvh - 60px);
min-height: 0;
transform: translateX(-100%); transform: translateX(-100%);
transition: transform 0.3s ease-in-out; transition: transform 0.3s ease-in-out;
z-index: 999; z-index: 999;

View File

@@ -131,15 +131,38 @@ export function GalleryManager() {
this.dragSrcI = index; this.dragSrcI = index;
}, },
moveImageUp(index){
if( index <= 0 )
return;
const moved = this.images.splice(index, 1)[0];
this.images.splice(index - 1, 0, moved);
},
moveImageDown(index){
if( index >= this.images.length - 1 )
return;
const moved = this.images.splice(index, 1)[0];
this.images.splice(index + 1, 0, moved);
},
reorderImages(from, to){
if( from === null || to === null || from === to )
return;
const moved = this.images.splice(from, 1)[0];
this.images.splice(to, 0, moved);
this.dragSrcI = to;
},
dragOver(e, index){ dragOver(e, index){
e.preventDefault(); e.preventDefault();
if( this.dragSrcI === null || this.dragSrcI === index ) if( this.dragSrcI === null || this.dragSrcI === index )
return; return;
const moved = this.images.splice(this.dragSrcI, 1)[0]; this.reorderImages(this.dragSrcI, index);
this.images.splice(index, 0, moved);
this.dragSrcI = index;
}, },
dragEnd(){ dragEnd(){

View File

@@ -14,6 +14,11 @@ export function HashesManager( wire ) {
*/ */
isCalculating: false, isCalculating: false,
/**
*
*/
manualHashesModal: false,
/** /**
* An error on hash calculation. * An error on hash calculation.
* @type {any|null} * @type {any|null}

View File

@@ -8,6 +8,7 @@ import notifications from "./notifications.js";
import conversations from "./conversations.js"; import conversations from "./conversations.js";
import settings from "./settings.js"; import settings from "./settings.js";
import { initMobileMenu } from "./mobile-menu.js"; import { initMobileMenu } from "./mobile-menu.js";
import { initMobileBackToTop } from "./mobile-back-to-top.js"
/** /**
* Get config defined in meta.blade.php * Get config defined in meta.blade.php
@@ -47,3 +48,4 @@ Alpine.store('settings', settings() );
// Mobile Menu // Mobile Menu
document.addEventListener('DOMContentLoaded', initMobileMenu); document.addEventListener('DOMContentLoaded', initMobileMenu);
document.addEventListener( 'DOMContentLoaded', initMobileBackToTop );

View File

@@ -72,6 +72,7 @@ export default function hovercard(){
Alpine.nextTick(() => { Alpine.nextTick(() => {
const card = document.querySelector('.hovercard'); const card = document.querySelector('.hovercard');
if (card) window.refreshIcons(card); if (card) window.refreshIcons(card);
this.updatePosition(this.anchorEl);
}); });
} catch( error ){ } catch( error ){
@@ -89,13 +90,26 @@ export default function hovercard(){
const RECT = anchorEl.getBoundingClientRect(); const RECT = anchorEl.getBoundingClientRect();
const SCROLL_X = window.scrollX; const SCROLL_X = window.scrollX;
const SCROLL_Y = window.scrollY; const SCROLL_Y = window.scrollY;
const VIEWPORT_WIDTH = window.innerWidth;
const VIEWPORT_HEIGHT = window.innerHeight;
let x = RECT.left + SCROLL_X; const CARD = document.querySelector('.hovercard');
const WIDTH = CARD?.offsetWidth || 280;
const HEIGHT = CARD?.offsetHeight || 320;
let x = RECT.right + SCROLL_X + 8;
let y = RECT.bottom + SCROLL_Y + 8; let y = RECT.bottom + SCROLL_Y + 8;
const WIDTH = 280; if( x + WIDTH > VIEWPORT_WIDTH - 8 && RECT.left + SCROLL_X - WIDTH - 8 >= 8 ){
if( x + WIDTH > window.innerWidth ){ x = RECT.left + SCROLL_X - WIDTH - 8;
x = window.innerWidth - WIDTH - 16; } else {
x = Math.max(8, Math.min(x, VIEWPORT_WIDTH - WIDTH - 8));
}
if( y + HEIGHT > VIEWPORT_HEIGHT + SCROLL_Y - 8 && RECT.top + SCROLL_Y - HEIGHT - 8 >= 8 ){
y = RECT.top + SCROLL_Y - HEIGHT - 8;
} else {
y = Math.max(8, Math.min(y, VIEWPORT_HEIGHT + SCROLL_Y - HEIGHT - 8));
} }
this.x = x; this.x = x;

View File

@@ -0,0 +1,18 @@
export function initMobileBackToTop(){
const backToTopButton = document.querySelector('.back-to-top');
const content = document.getElementById('content');
if (!backToTopButton || !content) {
return;
}
const toggleBackToTop = () => {
const shouldShow = window.innerWidth <= 768 && content.scrollTop > 320;
backToTopButton.classList.toggle('visible', shouldShow);
};
toggleBackToTop();
content.addEventListener('scroll', toggleBackToTop, { passive: true });
window.addEventListener('resize', toggleBackToTop, { passive: true });
}

View File

@@ -20,7 +20,7 @@
</div> </div>
<div style="margin-bottom:10px"> <div style="margin-bottom:10px">
<span class="badge {{ $entry->type }}">{{ \App\View\Components\EntryCard::ENTRY_TYPES_BADGE[$entry->type] ?? $entry->type }}</span> <span class="badge {{ $entry->type }}">{{ \App\View\Components\EntryCard::ENTRY_TYPES_BADGE[$entry->type] ?? $entry->type }}</span>
@if( section_must_be('romhacks', $entry->type ) ) @if( section_must_be(['romhacks', 'lua-scripts'], $entry->type ) )
@foreach( $entry->modifications as $modif ) @foreach( $entry->modifications as $modif )
<span class="badge orange">{{ $modif->name }}</span> <span class="badge orange">{{ $modif->name }}</span>
@endforeach @endforeach
@@ -28,7 +28,7 @@
@foreach( $entry->languages as $lang ) @foreach( $entry->languages as $lang )
<span class="badge orange">{{ $lang->name }}</span> <span class="badge orange">{{ $lang->name }}</span>
@endforeach @endforeach
@elseif( section_must_be( 'utilities', $entry->type ) ) @elseif( section_must_be( ['utilities', 'documents'], $entry->type ) )
@foreach( $entry->categories as $category ) @foreach( $entry->categories as $category )
<span class="badge orange">{{ $category->name }}</span> <span class="badge orange">{{ $category->name }}</span>
@endforeach @endforeach
@@ -46,7 +46,7 @@
@endif @endif
</div> </div>
<div class="entry-card-meta"> <div class="entry-card-meta">
<span><i data-lucide="download" size="12"></i> x</span> <span><i data-lucide="download" size="12"></i> {{ $entry->total_downloads ?? 0 }}</span>
<span>Added: {{ $entry->created_at->format('y-m-d') }}</span> <span>Added: {{ $entry->created_at->format('y-m-d') }}</span>
</div> </div>
</div> </div>

View File

@@ -2,20 +2,28 @@
<x-form-field-title name="Screenshots" helper="At least 1 Screenshot required, Maximum 20." required="{{ $required ? 'true' : 'false' }}" /> <x-form-field-title name="Screenshots" helper="At least 1 Screenshot required, Maximum 20." required="{{ $required ? 'true' : 'false' }}" />
<div class="form-group main-image-grid"> <div class="form-group main-image-grid">
<div class="form-upload" style="flex:1;" :class="{ 'disabled': isFull }"> <div class="form-upload" style="flex:1;" :class="{ 'disabled': isFull }">
<input type="file" id="gallery-field" accept="image/png, image/jpeg, image/webp" multiple :disabled="isFull" @change="handleSubmitFiles($event)"> <input type="file" id="gallery-field" accept="image/png, image/jpeg, image/webp, image/gif" multiple :disabled="isFull" @change="handleSubmitFiles($event)">
<div class="form-upload-placeholder level"> <div class="form-upload-placeholder level">
<i data-lucide="file-archive" size="36" style="margin-bottom:15px;color:var(--text2)"></i> <i data-lucide="file-archive" size="36" style="margin-bottom:15px;color:var(--text2)"></i>
<div style="font-size: 1.1rem;color:var(--text);margin-bottom:5px;"> <div style="font-size: 1.1rem;color:var(--text);margin-bottom:5px;">
Click or drag'n drop files here. Click or drag'n drop files here.
</div> </div>
<div style="font-size:0.85rem;color:var(--text2);"> <div style="font-size:0.85rem;color:var(--text2);">
Accepted: PNG, JPG or WebP Accepted: PNG, JPG, GIF or WebP
</div> </div>
</div> </div>
</div> </div>
<div class="form-gallery form-group level" style="flex:4;"> <div class="form-gallery form-group level" style="flex:4;">
<template x-for="(image,i) in images" :key="image.key"> <template x-for="(image,i) in images" :key="image.key">
<div class="gallery-item" :class="{ 'gallery-item--dragging': dragSrcI === i }" draggable="true" @dragstart="dragStart(i)" @dragover="dragOver($event, i)" @dragend="dragEnd()"> <div
class="gallery-item"
:class="{ 'gallery-item--dragging': dragSrcI === i }"
draggable="true"
:data-gallery-index="i"
@dragstart="dragStart(i)"
@dragover="dragOver($event, i)"
@dragend="dragEnd()"
>
<div class="form-image-preview-wrap"> <div class="form-image-preview-wrap">
<div class="gallery-drag-handle" title="Drag to reorder"> <div class="gallery-drag-handle" title="Drag to reorder">
<i data-lucide="grip-vertical" size="14"></i> <i data-lucide="grip-vertical" size="14"></i>
@@ -28,6 +36,14 @@
X X
</button> </button>
</div> </div>
<div class="gallery-mobile-controls" aria-label="Reorder screenshot">
<button type="button" class="gallery-move-btn" @click="moveImageUp(i)" :disabled="i === 0" title="Move up">
<i data-lucide="chevron-up" size="14"></i>
</button>
<button type="button" class="gallery-move-btn" @click="moveImageDown(i)" :disabled="i === images.length - 1" title="Move down">
<i data-lucide="chevron-down" size="14"></i>
</button>
</div>
</div> </div>
</template> </template>
</div> </div>

View File

@@ -3,14 +3,14 @@
<x-form-field-title name="Main image" helper="This will show up on the index and on top of the entry. A screenshot or custom cover is prefered else all entries of same game will look the same." required="{{ $required ? 'true' : 'false' }}" /> <x-form-field-title name="Main image" helper="This will show up on the index and on top of the entry. A screenshot or custom cover is prefered else all entries of same game will look the same." required="{{ $required ? 'true' : 'false' }}" />
<div class="form-group main-image-grid"> <div class="form-group main-image-grid">
<div class="form-upload" style="flex:4;"> <div class="form-upload" style="flex:4;">
<input type="file" id="main-image-field" accept="image/png, image/jpeg, image/webp" @change="handleSubmitFile($event)"> <input type="file" id="main-image-field" accept="image/png, image/jpeg, image/webp, image/gif" @change="handleSubmitFile($event)">
<div class="form-upload-placeholder level"> <div class="form-upload-placeholder level">
<i data-lucide="file-archive" size="36" style="margin-bottom:15px;color:var(--text2)"></i> <i data-lucide="file-archive" size="36" style="margin-bottom:15px;color:var(--text2)"></i>
<div style="font-size: 1.1rem;color:var(--text);margin-bottom:5px;"> <div style="font-size: 1.1rem;color:var(--text);margin-bottom:5px;">
Click or drag'n drop files here. Click or drag'n drop files here.
</div> </div>
<div style="font-size:0.85rem;color:var(--text2);"> <div style="font-size:0.85rem;color:var(--text2);">
Accepted: PNG, JPG or WebP Accepted: PNG, JPG, GIF or WebP
</div> </div>
</div> </div>
<input type="hidden" name="main-image" x-model="serverFilePath"> <input type="hidden" name="main-image" x-model="serverFilePath">

View File

@@ -2,9 +2,9 @@
<x-form-field-title name="Staff/Credits" /> <x-form-field-title name="Staff/Credits" />
<template x-if="credits.length > 0"> <template x-if="credits.length > 0">
<div class="form-group grid-credits"> <div class="form-group grid-credits">
<div><x-form-field-title name="Name" /></div> <div class="grid-first"><x-form-field-title name="Name" /></div>
<div><x-form-field-title name="Description" /></div> <div class="grid-first"><x-form-field-title name="Description" /></div>
<div><x-form-field-title name="Actions" /></div> <div class="grid-first"><x-form-field-title name="Actions" /></div>
<template x-for="(credit,i) in credits" :key="i"> <template x-for="(credit,i) in credits" :key="i">
<div style="display:contents"> <div style="display:contents">
<div> <div>

View File

@@ -1,6 +1,6 @@
@php $topbarModSeparator = false; $topbarAdminSeparator = false; @endphp @php $topbarModSeparator = false; $topbarAdminSeparator = false; @endphp
<header id="topbar"> <header id="topbar">
<button class="mobile-toggle"> <button class="mobile-toggle" title="Open menu">
<i data-lucide="menu"></i> <i data-lucide="menu"></i>
</button> </button>
@@ -14,7 +14,7 @@
<input type="text" id="search-input" placeholder="Search" autocomplete="off"> <input type="text" id="search-input" placeholder="Search" autocomplete="off">
<button type="submit" class="search-button"> <button type="submit" class="search-button" title="Search">
<i data-lucide="search" size="18" color="var(--text2)"></i> <i data-lucide="search" size="18" color="var(--text2)"></i>
</button> </button>
</form> </form>
@@ -124,26 +124,28 @@
@endcan @endcan
@if( !\Auth::guest() ) @if( !\Auth::guest() )
<div x-data x-init="$store.notifications.unviewed = {{ \Auth::user()->alerts_unviewed }}" style="position:relative"> <div x-data x-init="$store.notifications.unviewed = {{ \Auth::user()->alerts_unviewed }}" style="position:relative">
<button type="button" class="btn" :class="{ 'active': $store.notifications.start }" @click="$store.notifications.open($el)" @click.outside="$store.notifications.close()"> <button type="button" class="btn" title="Notifications" :class="{ 'active': $store.notifications.start }" @click="$store.notifications.open($el)" @click.outside="$store.notifications.close()">
<i data-lucide="bell" size="18"></i> <i data-lucide="bell" size="18"></i>
<span <span
class="topbar-badge" class="topbar-badge"
:class="$store.notifications.unread > 9 ? 'topbar-badge--overflow' : ''" :class="$store.notifications.unread > 9 ? 'topbar-badge--overflow' : ''"
x-show="$store.notifications.unread > 0" x-show="$store.notifications.unread > 0"
x-text="$store.notifications.unread > 99 ? '99+' : $store.notifications.unread" x-text="$store.notifications.unread > 99 ? '99+' : $store.notifications.unread"
x-cloak
></span> ></span>
</button> </button>
@include('components.notifications') @include('components.notifications')
</div> </div>
<div x-data x-init="$store.conversations.unviewed = {{ \Auth::user()->conversations_unread }}" style="position:relative"> <div x-data x-init="$store.conversations.unviewed = {{ \Auth::user()->conversations_unread }}" style="position:relative">
<button type="button" class="btn" :class="{ 'active': $store.conversations.start }" @click="$store.conversations.open($el)" @click.outside="$store.conversations.close()"> <button type="button" class="btn" title="Messages" :class="{ 'active': $store.conversations.start }" @click="$store.conversations.open($el)" @click.outside="$store.conversations.close()">
<i data-lucide="mail" size="18"></i> <i data-lucide="mail" size="18"></i>
<span <span
class="topbar-badge" class="topbar-badge"
:class="$store.conversations.unread > 9 ? 'topbar-badge--overflow' : ''" :class="$store.conversations.unread > 9 ? 'topbar-badge--overflow' : ''"
x-show="$store.conversations.unread > 0" x-show="$store.conversations.unread > 0"
x-text="$store.conversations.unread > 99 ? '99+' : $store.conversations.unread" x-text="$store.conversations.unread > 99 ? '99+' : $store.conversations.unread"
x-cloak
></span> ></span>
</button> </button>
@@ -154,6 +156,7 @@
<button <button
type="button" type="button"
class="btn" class="btn"
title="Settings"
:class="{ 'active': $store.settings.start }" :class="{ 'active': $store.settings.start }"
@click="$store.settings.open()" @click="$store.settings.open()"
@click.outside="$store.settings.close()" @click.outside="$store.settings.close()"

View File

@@ -54,7 +54,7 @@
</div> </div>
<div class="comment-body"> <div class="comment-body">
{!! $comment['message'] !!} {!! \App\Helpers\EntryHelpers::stripBbCode($comment['message']) !!}
</div> </div>
</div> </div>
</div> </div>

View File

@@ -122,6 +122,9 @@
<x-entry-meta-item label="Release Date" value="{{ $entry->release_date->format('Y-m-d') }}" <x-entry-meta-item label="Release Date" value="{{ $entry->release_date->format('Y-m-d') }}"
route="none"/> route="none"/>
@endif @endif
@if( $entry->total_downloads )
<x-entry-meta-item label="Downloads" value="{{ $entry->total_downloads }}" route="none" />
@endif
</div> </div>
<div class="hack-actions" style="display:flex;gap:10px;"> <div class="hack-actions" style="display:flex;gap:10px;">
@if($entry->state === 'pending') @if($entry->state === 'pending')

View File

@@ -0,0 +1,6 @@
@extends('layouts.app')
@section('content')
<x-error-block error-type="page-not-allowed" message="Unknown" />
<a class="btn" href="javascript:history.go(-1)">Go back to the previous page</a>
@endsection

View File

@@ -0,0 +1,6 @@
@extends('layouts.app')
@section('content')
<x-error-block error-type="not-found" />
<a class="btn" href="javascript:history.go(-1)">Go back to the previous page</a>
@endsection

View File

@@ -0,0 +1,6 @@
@extends('layouts.app')
@section('content')
<x-error-block error-type="custom" message="{{ $exception->getMessage() }}" />
<a class="btn" href="javascript:history.go(-1)">Go back to the previous page</a>
@endsection

View File

@@ -0,0 +1,305 @@
<html>
<head>
<title>500 - Internal server error - Romhack Plaza</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
:root {
/* RHPZ color */
--rhpz-orange: #ff7300;
--rhpz-orange-hover: #e56700;
/* Background colors */
--bg: #1e1e1e;
--bg2: #252526;
--bg3: #2d2d30;
--bg4: #3e3e42;
/* Text */
--text: #f1f1f1;
--text2: #a1a1aa;
--text3: #111111;
/* Elements */
--border: #3f3f46;
--error: #e57373;
--info: #1976d2;
--success: #81c784;
--success2: #388e3c;
/* Typo */
--typography: 'Segoe UI', 'San Francisco', 'Helvetica Neue', sans-serif;
/* Menu settings */
--menu-size: 260px;
--menu-user-avatar-bg: #555;
/* Gap */
--gap: 15px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--typography);
background-color: var(--bg);
color: var(--text);
display: flex;
height: 100vh;
overflow: hidden;
}
a {
color: var(--rhpz-orange);
text-decoration: none;
transition: color 0.2s;
}
a:hover {
color: var(--rhpz-orange-hover);
text-decoration: underline;
}
#app {
display: flex;
width: 100%;
height: 100%;
}
ul {
margin-left: 20px;
margin-bottom: 20px;
list-style-type: square;
}
[x-cloak] {display: none !important;}
/* BUTTONS */
.btn {
background-color: var(--bg3);
border: 1px solid var(--border);
color: var(--text);
padding: 8px 16px;
font-size: 0.9rem;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.1s;
}
.btn:hover {
background-color: var(--bg4);
border-color: var(--menu-user-avatar-bg);
}
.btn.primary {
background-color: var(--rhpz-orange);
color: var(--text3);
border-color: var(--rhpz-orange);
font-weight: 600;
}
.btn.primary:hover {
background-color: var(--rhpz-orange-hover);
border-color: var(--rhpz-orange-hover);
}
.btn.danger {
background-color: transparent;
color: var(--error);
border-color: var(--error);
}
.btn.danger:hover {
background-color: rgba(229, 115, 115, 0.1);
}
.btn.success {
background-color: transparent;
color: var(--success);
border-color: var(--success);
}
.btn.success:hover {
background-color: rgba(129, 199, 132, 0.1);
}
/* BLOCK */
.block {
background-color: var(--bg2);
border: 1px solid var(--border);
padding: 20px;
margin-bottom: 20px;
}
.block.featured {
border-left: 3px solid var(--rhpz-orange);
}
.block-header {
font-size: 1.2rem;
color: var(--text);
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
border-bottom: 1px solid var(--border);
padding-bottom: 10px;
}
.block-success {
background-color: var(--success);
border: 1px solid var(--success);
color: var(--text);
padding: 20px;
margin-bottom: 20px;
}
.block-error {
background-color: var(--error);
border: 1px solid var(--error);
color: var(--text);
padding: 20px;
margin-bottom: 20px;
}
/* PAGE */
.page-title {
font-size: 1.8rem;
font-weight: 300;
margin-bottom: 20px;
color: var(--text);
flex-shrink: 0;
}
/* TEXTS */
.whisper {
color: var(--text2);
margin-bottom: 15px;
}
.content-title {
color: var(--text);
margin: 30px 0 15px 0;
border-left: 3px solid var(--rhpz-orange);
padding-left: 10px;
}
.quote {
background-color: var(--bg);
border-left: 4px solid #1976d2;
padding: 15px;
margin-top: 30px;
font-style: italic;
}
@media (max-width: 768px) {
.btn {
padding: 7px 12px;
font-size: 0.85rem;
gap: 6px;
}
.block {
padding: 15px;
margin-bottom: 15px;
}
.block-header {
font-size: 1.05rem;
margin-bottom: 12px;
padding-bottom: 8px;
}
.page-title {
font-size: 1.5rem;
margin-bottom: 15px;
}
.content-title {
margin: 20px 0 12px 0;
padding-left: 8px;
}
.quote {
padding: 12px;
margin-top: 20px;
font-size: 0.95rem;
}
.whisper {
margin-bottom: 12px;
font-size: 0.9rem;
}
.breadcrumb {
font-size: 0.85rem;
}
}
@media (max-width: 600px) {
.btn {
padding: 6px 10px;
font-size: 0.8rem;
gap: 4px;
justify-content: center;
}
.btn.primary, .btn.danger, .btn.success {
width: 100%;
}
.block {
padding: 12px;
margin-bottom: 12px;
}
.block-header {
font-size: 0.95rem;
margin-bottom: 10px;
padding-bottom: 6px;
}
.page-title {
font-size: 1.2rem;
margin-bottom: 12px;
}
.badge {
padding: 2px 6px;
font-size: 0.7rem;
}
.topbar-badge {
min-width: 16px;
height: 16px;
padding: 0 3px;
font-size: 0.6rem;
}
}
</style>
</head>
<body>
<div id="app" style="display:flex;flex-direction: column;align-items: center;justify-content: center;">
<main id="main-wrapper">
<main id="content">
<div class="block">
<div class="page-title">500 - Internal server error</div>
<p>To resolve the issue quickly, please report it on Gitea, Discord, or the forum with the following information.</p>
<ul>
<li>Your error code: {{ $errorId ?? "" }}</li>
<li>What you were doing before the error occurred.</li>
</ul>
</div>
<div>
<a href="https://code.romhackplaza.org/RHPZAdmin/RomhackPlaza/issues" class="btn primary">Report the problem on Gitea</a>
<a href="https://community.romhackplaza.org/forums/bug-reports.6/" class="btn primary">Report the problem on the forum</a>
<a href="https://community.romhackplaza.org/misc/contact/" class="btn">Report the problem with the contact form</a>
<a href="https://discord.gg/5CKzeWmZZU" class="btn">Report the problem on Discord</a>
</div>
</main>
</main>
</div>
</body>
</html>

View File

@@ -24,12 +24,20 @@
@if(session('error')) @if(session('error'))
<x-error-block error-type="custom" :message="session('error')" /> <x-error-block error-type="custom" :message="session('error')" />
@endif @endif
@if(isset($errors) && $errors->has('maintenance'))
<x-error-block error-type="custom" :message="$errors->get('maintenance')[0]" />
@endif
@yield('content') @yield('content')
</div> </div>
</main> </main>
</div> </div>
<button type="button" class="back-to-top" aria-label="Back to top" onclick="document.getElementById('content')?.scrollTo({ top: 0, behavior: 'smooth' });">
<i data-lucide="arrow-up"></i>
<span>Top</span>
</button>
@include('components.hovercard') @include('components.hovercard')
@livewireScripts @livewireScripts
@stack('scripts') @stack('scripts')

View File

@@ -38,7 +38,11 @@
</div> </div>
<div class="menu-user-info"> <div class="menu-user-info">
<span class="username"> <span class="username">
{{ $VISITOR->username ?? "Guest" }} @if( $VISITOR->guest() )
Guest
@else
<x-xf-username-link :user-id="$VISITOR->user_id" />
@endif
</span> </span>
<span class="user_role"> <span class="user_role">
<a href="{{ $VISITOR->guest() ? xfRoute('login') : xfRoute('logout') . '?t=' . xfCsrfToken() }}"> <a href="{{ $VISITOR->guest() ? xfRoute('login') : xfRoute('logout') . '?t=' . xfCsrfToken() }}">

View File

@@ -27,10 +27,12 @@
<div class="modcp-nav-group"> <div class="modcp-nav-group">
<span class="modcp-nav-label">Content</span> <span class="modcp-nav-label">Content</span>
@can('moderate','\App\Models\Entry')
<a href="{{ route('modcp.locked') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.locked') ? 'active' : '' }}> <a href="{{ route('modcp.locked') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.locked') ? 'active' : '' }}>
<i data-lucide="lock" size="15"></i> <i data-lucide="lock" size="15"></i>
Locked entries Locked entries
</a> </a>
@endcan
@can('is-admin') @can('is-admin')
<a href="{{ route('modcp.draft') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.draft') ? 'active' : '' }}> <a href="{{ route('modcp.draft') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.draft') ? 'active' : '' }}>
<i data-lucide="scissors" size="15"></i> <i data-lucide="scissors" size="15"></i>
@@ -74,6 +76,14 @@
<i data-lucide="box" size="15"></i> <i data-lucide="box" size="15"></i>
Genres Genres
</a> </a>
<a href="{{ route('modcp.levels.index') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.levels.*') ? 'active' : '' }}">
<i data-lucide="weight" size="15"></i>
Levels
</a>
<a href="{{ route('modcp.modifications.index') }}" class="modcp-nav-item" {{ request()->routeIs('modcp.modifications.*') ? 'active' : '' }}">
<i data-lucide="pencil-ruler" size="15"></i>
Modifications
</a>
@endcan @endcan
</div> </div>

View File

@@ -203,7 +203,7 @@
</div> </div>
<div class="log-pagination"> <div class="log-pagination">
{{ $logs->links() }} {{ $logs->links('modcp.pagination') }}
</div> </div>
@endif @endif

View File

@@ -59,7 +59,55 @@
<span x-show="error" x-text="error" class="form-error-text" x-cloak></span> <span x-show="error" x-text="error" class="form-error-text" x-cloak></span>
<button type="button" class="btn primary" :disabled="isCalculating" @click="handleSubmitFile()">Add Hashes</button> <button type="button" class="btn" :disabled="isCalculating" @click="manualHashesModal = true;">Provide hash manually</button>
<button type="button" class="btn primary" :disabled="isCalculating" @click="handleSubmitFile()">Add hashes</button>
</div>
<div
class="modal-overlay"
x-show="manualHashesModal"
x-effect="manualHashesModal && $nextTick(() => window.refreshIcons($el))"
x-cloak
x-transition.opacity.duration.300ms
@keydown.escape.window="manualHashesModal = false"
@focus="window.refreshIcons($el)"
@close-manual-modal.window="manualHashesModal = false"
>
<div @click.outside="manualHashesModal = false" class="modal-window">
<div class="modal-header">
<span class="modal-title">Provide hash manually</span>
<button type="button" @click="manualHashesModal = false" class="modal-close">
<i data-lucide="x" size="18"></i>
</button>
</div>
<div class="modal-content">
<div class="form-group">
<label class="form-label">Filename</label>
<input type="text" wire:model="manualFilename" class="form-input" maxlength="512">
@error('manualFilename') <span class="form-error-text">{{ $message }}</span> @enderror
</div>
<div class="form-group">
<label class="form-label">CRC32</label>
<input type="text" wire:model="manualCRC32" class="form-input" maxlength="512">
@error('manualCRC32') <span class="form-error-text">{{ $message }}</span> @enderror
</div>
<div class="form-group">
<label class="form-label">SHA-1</label>
<input type="text" wire:model="manualSHA1" class="form-input" maxlength="512">
@error('manualSHA1') <span class="form-error-text">{{ $message }}</span> @enderror
</div>
<button type="button" class="btn primary" style="width: 100%; justify-content: center;" wire:click="addManualHash" wire:loading.attr="disabled">
<i data-lucide="plus" size="14"></i> Add
</button>
</div>
</div>
</div> </div>
</div> </div>

View File

@@ -0,0 +1,299 @@
<html>
<head>
<title>500 - Internal server error - Romhack Plaza</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
:root {
/* RHPZ color */
--rhpz-orange: #ff7300;
--rhpz-orange-hover: #e56700;
/* Background colors */
--bg: #1e1e1e;
--bg2: #252526;
--bg3: #2d2d30;
--bg4: #3e3e42;
/* Text */
--text: #f1f1f1;
--text2: #a1a1aa;
--text3: #111111;
/* Elements */
--border: #3f3f46;
--error: #e57373;
--info: #1976d2;
--success: #81c784;
--success2: #388e3c;
/* Typo */
--typography: 'Segoe UI', 'San Francisco', 'Helvetica Neue', sans-serif;
/* Menu settings */
--menu-size: 260px;
--menu-user-avatar-bg: #555;
/* Gap */
--gap: 15px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--typography);
background-color: var(--bg);
color: var(--text);
display: flex;
height: 100vh;
overflow: hidden;
}
a {
color: var(--rhpz-orange);
text-decoration: none;
transition: color 0.2s;
}
a:hover {
color: var(--rhpz-orange-hover);
text-decoration: underline;
}
#app {
display: flex;
width: 100%;
height: 100%;
}
ul {
margin-left: 20px;
margin-bottom: 20px;
list-style-type: square;
}
[x-cloak] {display: none !important;}
/* BUTTONS */
.btn {
background-color: var(--bg3);
border: 1px solid var(--border);
color: var(--text);
padding: 8px 16px;
font-size: 0.9rem;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.1s;
}
.btn:hover {
background-color: var(--bg4);
border-color: var(--menu-user-avatar-bg);
}
.btn.primary {
background-color: var(--rhpz-orange);
color: var(--text3);
border-color: var(--rhpz-orange);
font-weight: 600;
}
.btn.primary:hover {
background-color: var(--rhpz-orange-hover);
border-color: var(--rhpz-orange-hover);
}
.btn.danger {
background-color: transparent;
color: var(--error);
border-color: var(--error);
}
.btn.danger:hover {
background-color: rgba(229, 115, 115, 0.1);
}
.btn.success {
background-color: transparent;
color: var(--success);
border-color: var(--success);
}
.btn.success:hover {
background-color: rgba(129, 199, 132, 0.1);
}
/* BLOCK */
.block {
background-color: var(--bg2);
border: 1px solid var(--border);
padding: 20px;
margin-bottom: 20px;
}
.block.featured {
border-left: 3px solid var(--rhpz-orange);
}
.block-header {
font-size: 1.2rem;
color: var(--text);
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 10px;
border-bottom: 1px solid var(--border);
padding-bottom: 10px;
}
.block-success {
background-color: var(--success);
border: 1px solid var(--success);
color: var(--text);
padding: 20px;
margin-bottom: 20px;
}
.block-error {
background-color: var(--error);
border: 1px solid var(--error);
color: var(--text);
padding: 20px;
margin-bottom: 20px;
}
/* PAGE */
.page-title {
font-size: 1.8rem;
font-weight: 300;
margin-bottom: 20px;
color: var(--text);
flex-shrink: 0;
}
/* TEXTS */
.whisper {
color: var(--text2);
margin-bottom: 15px;
}
.content-title {
color: var(--text);
margin: 30px 0 15px 0;
border-left: 3px solid var(--rhpz-orange);
padding-left: 10px;
}
.quote {
background-color: var(--bg);
border-left: 4px solid #1976d2;
padding: 15px;
margin-top: 30px;
font-style: italic;
}
@media (max-width: 768px) {
.btn {
padding: 7px 12px;
font-size: 0.85rem;
gap: 6px;
}
.block {
padding: 15px;
margin-bottom: 15px;
}
.block-header {
font-size: 1.05rem;
margin-bottom: 12px;
padding-bottom: 8px;
}
.page-title {
font-size: 1.5rem;
margin-bottom: 15px;
}
.content-title {
margin: 20px 0 12px 0;
padding-left: 8px;
}
.quote {
padding: 12px;
margin-top: 20px;
font-size: 0.95rem;
}
.whisper {
margin-bottom: 12px;
font-size: 0.9rem;
}
.breadcrumb {
font-size: 0.85rem;
}
}
@media (max-width: 600px) {
.btn {
padding: 6px 10px;
font-size: 0.8rem;
gap: 4px;
justify-content: center;
}
.btn.primary, .btn.danger, .btn.success {
width: 100%;
}
.block {
padding: 12px;
margin-bottom: 12px;
}
.block-header {
font-size: 0.95rem;
margin-bottom: 10px;
padding-bottom: 6px;
}
.page-title {
font-size: 1.2rem;
margin-bottom: 12px;
}
.badge {
padding: 2px 6px;
font-size: 0.7rem;
}
.topbar-badge {
min-width: 16px;
height: 16px;
padding: 0 3px;
font-size: 0.6rem;
}
}
</style>
</head>
<body>
<div id="app" style="display:flex;flex-direction: column;align-items: center;justify-content: center;">
<main id="main-wrapper">
<main id="content">
<div class="block">
<div class="page-title">Maintenance is in progress.</div>
<p>Please wait until maintenance is complete. Follow the Discord server or the forum if this is unscheduled maintenance.</p>
</div>
<div>
<a href="https://community.romhackplaza.org/" class="btn primary">Go to the forum</a>
<a href="https://discord.gg/5CKzeWmZZU" class="btn">Go to Discord</a>
</div>
</main>
</main>
</div>
</body>
</html>

View File

@@ -84,6 +84,6 @@
@endforelse @endforelse
</div> </div>
{{ $items->links() }} {{ $items->links('modcp.pagination') }}
@endsection @endsection

View File

@@ -55,7 +55,7 @@
</div> </div>
@endforeach @endforeach
</div> </div>
{{ $entries->links() }} {{ $entries->links('modcp.pagination') }}
@endif @endif
@endsection @endsection

View File

@@ -47,6 +47,6 @@
</div> </div>
@endforeach @endforeach
</div> </div>
{{ $entries->links() }} {{ $entries->links( 'modcp.pagination' ) }}
@endif @endif
@endsection @endsection

View File

@@ -98,6 +98,6 @@
@endforelse @endforelse
</div> </div>
{{ $items->links() }} {{ $items->links('modcp.pagination') }}
@endsection @endsection

View File

@@ -14,6 +14,7 @@
<span class="modcp-stat-label">In queue</span> <span class="modcp-stat-label">In queue</span>
</div> </div>
</a> </a>
@can('moderate','\App\Models\Entry')
<a href="{{ route('modcp.locked') }}" class="modcp-stat-card"> <a href="{{ route('modcp.locked') }}" class="modcp-stat-card">
<div class="modcp-stat-icon"><i data-lucide="lock" size="22"></i></div> <div class="modcp-stat-icon"><i data-lucide="lock" size="22"></i></div>
<div class="modcp-stat-info"> <div class="modcp-stat-info">
@@ -21,6 +22,7 @@
<span class="modcp-stat-label">Locked</span> <span class="modcp-stat-label">Locked</span>
</div> </div>
</a> </a>
@endcan
@can('is-admin') @can('is-admin')
<a href="{{ route('modcp.draft') }}" class="modcp-stat-card"> <a href="{{ route('modcp.draft') }}" class="modcp-stat-card">
<div class="modcp-stat-icon"><i data-lucide="scissors" size="22"></i></div> <div class="modcp-stat-icon"><i data-lucide="scissors" size="22"></i></div>
@@ -54,6 +56,7 @@
</div> </div>
@if($recentDeleted->isNotEmpty()) @if($recentDeleted->isNotEmpty())
@can('is-admin')
<div class="modcp-section-title" style="margin-top: 25px;">Recently deleted</div> <div class="modcp-section-title" style="margin-top: 25px;">Recently deleted</div>
<div class="modcp-list"> <div class="modcp-list">
@foreach($recentDeleted as $entry) @foreach($recentDeleted as $entry)
@@ -85,6 +88,7 @@
<a href="{{ route('modcp.deleted') }}" class="modcp-list-see-all"> <a href="{{ route('modcp.deleted') }}" class="modcp-list-see-all">
See all deleted entries See all deleted entries
</a> </a>
@endcan
</div> </div>
@endif @endif

View File

@@ -0,0 +1,34 @@
<?php /** @var \Illuminate\Pagination\LengthAwarePaginator $paginator */ ?>
@if ($paginator->hasPages())
<div class="database-pagination">
@if ($paginator->onFirstPage())
<button class="btn" disabled>«</button>
@else
<a class="btn" href="{{ $paginator->previousPageUrl() }}">«</a>
@endif
{{-- Pages --}}
@foreach ($elements as $element)
@if (is_string($element))
<button class="btn" disabled>{{ $element }}</button>
@endif
@if (is_array($element))
@foreach ($element as $page => $url)
<a
class="btn {{ $page == $paginator->currentPage() ? 'active' : '' }}"
href="{{ $url }}"
>{{ $page }}</a>
@endforeach
@endif
@endforeach
@if ($paginator->hasMorePages())
<a class="btn" href="{{ $paginator->nextPageUrl() }}">»</a>
@else
<button class="btn" disabled>»</button>
@endif
</div>
@endif

View File

@@ -71,6 +71,6 @@
@endforelse @endforelse
</div> </div>
{{ $items->links() }} {{ $items->links('modcp.pagination') }}
@endsection @endsection

View File

@@ -33,8 +33,8 @@
</div> </div>
<div class="form-group" x-ref="descriptionField"> <div class="form-group" x-ref="descriptionField">
<x-form-field-title name="Content" required="true" /> <x-form-field-title name="Content" helper="You must write in markdown. Click on the interrogation mark in the toolbar to learn more about Markdown." required="true" />
<x-markdown-textarea name="description" value="{{ old('description', $news->description ?? '') }}" /> <x-markdown-textarea name="description" value="{!! old('description', $news->description ?? '') !!}" />
<div class="form-error-text" x-show="errorKey === 'noDescription'" x-text="errorMessage"></div> <div class="form-error-text" x-show="errorKey === 'noDescription'" x-text="errorMessage"></div>
@error('description') @error('description')
<x-form-error-text message="{{ $message }}" /> <x-form-error-text message="{{ $message }}" />

View File

@@ -154,6 +154,7 @@
</div> </div>
@endif @endif
</div> </div>
@if( $news->entry()->exists() || $news->relevant_link || $news->youtube_link )
<aside class="news-sidebar"> <aside class="news-sidebar">
@if($news->entry()->exists()) @if($news->entry()->exists())
@@ -219,6 +220,7 @@
</div> </div>
@endif @endif
</aside> </aside>
@endif
</div> </div>
</article> </article>
@include('entries.comments') @include('entries.comments')

View File

@@ -11,7 +11,7 @@
<div class="modal-header"> <div class="modal-header">
<span class="modal-title">Write a review</span> <span class="modal-title">Write a review</span>
<button @click="reviewModalOpen = false" class="modal-close"> <button type="button" @click="reviewModalOpen = false" class="modal-close">
<i data-lucide="x" size="18"></i> <i data-lucide="x" size="18"></i>
</button> </button>
</div> </div>

View File

@@ -120,8 +120,8 @@
@endif @endif
<div class="form-group" x-ref="descriptionField"> <div class="form-group" x-ref="descriptionField">
<x-form-field-title name="{{ $words['description'] }}" required="true" /> <x-form-field-title name="{{ $words['description'] }}" helper="You must write in markdown. Click on the interrogation mark in the toolbar to learn more about Markdown." required="true" />
<x-markdown-textarea name="description" value="{{ old('description', $entry->description ?? '') }}" /> <x-markdown-textarea name="description" value="{!! old('description', $entry->description ?? '') !!}" />
<div class="form-error-text" x-show="errorKey === 'noDescription'" x-text="errorMessage"></div> <div class="form-error-text" x-show="errorKey === 'noDescription'" x-text="errorMessage"></div>
@error('description') @error('description')
<x-form-error-text message="{{ $message }}" /> <x-form-error-text message="{{ $message }}" />

View File

@@ -1,7 +1,6 @@
@if ($paginator->hasPages()) @if ($paginator->hasPages())
<div class="database-pagination"> <div class="database-pagination">
{{-- Précédent --}}
@if ($paginator->onFirstPage()) @if ($paginator->onFirstPage())
<button class="btn" disabled>«</button> <button class="btn" disabled>«</button>
@else @else
@@ -24,7 +23,6 @@
@endif @endif
@endforeach @endforeach
{{-- Suivant --}}
@if ($paginator->hasMorePages()) @if ($paginator->hasMorePages())
<button class="btn" wire:click="nextPage">»</button> <button class="btn" wire:click="nextPage">»</button>
@else @else

View File

@@ -33,14 +33,14 @@ Route::name('reviews.')->controller(ReviewController::class)->group(function ()
}); });
// SubmissionController. // SubmissionController.
Route::name('submit.')->prefix('/submit')->controller(\App\Http\Controllers\SubmissionController::class)->middleware(['xf.auth', 'can:create,\App\Models\Entry'])->group(function () { Route::name('submit.')->prefix('/submit')->controller(\App\Http\Controllers\SubmissionController::class)->middleware(['submissions.maintenance','xf.auth', 'can:create,\App\Models\Entry'])->group(function () {
Route::get('/', 'index')->name('index'); Route::get('/', 'index')->name('index');
Route::get('/{section}', 'create' )->name('create') Route::get('/{section}', 'create' )->name('create')
->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts' ]); ->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts' ]);
Route::post('/{section}', 'store' )->name('store') Route::post('/{section}', 'store' )->name('store')
->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts' ]); ->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts' ]);
}); });
Route::name('submit.')->prefix('/edit')->controller(\App\Http\Controllers\SubmissionController::class)->middleware(['xf.auth', 'can:update,entry'])->group(function () { Route::name('submit.')->prefix('/edit')->controller(\App\Http\Controllers\SubmissionController::class)->middleware(['submissions.maintenance','xf.auth', 'can:update,entry'])->group(function () {
Route::get('/{section}/{entry:id}', 'edit' )->name('edit') Route::get('/{section}/{entry:id}', 'edit' )->name('edit')
->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts', 'entry' => '[0-9\-]+' ]); ->where([ 'section' => 'translations|romhacks|homebrew|utilities|documents|lua-scripts', 'entry' => '[0-9\-]+' ]);
Route::post('/{section}/{entry:id}', 'update' )->name('update') Route::post('/{section}/{entry:id}', 'update' )->name('update')
@@ -54,12 +54,12 @@ Route::name('news.')->controller(\App\Http\Controllers\NewsController::class)->g
Route::get('/news/', 'index' )->name('index'); Route::get('/news/', 'index' )->name('index');
Route::get('/news/{news:slug}', 'show' )->name('show')->where([ 'news' => '[a-zA-Z0-9\-_]+']); Route::get('/news/{news:slug}', 'show' )->name('show')->where([ 'news' => '[a-zA-Z0-9\-_]+']);
Route::get('/submit/news', 'create' )->name('create')->middleware(['xf.auth','can:create,\App\Models\News']); Route::get('/submit/news', 'create' )->name('create')->middleware(['submissions.maintenance','xf.auth','can:create,\App\Models\News']);
Route::post('/submit/news', 'store' )->name('store')->middleware(['xf.auth','can:create,\App\Models\News']); Route::post('/submit/news', 'store' )->name('store')->middleware(['submissions.maintenance','xf.auth','can:create,\App\Models\News']);
Route::get('/edit/news/{news:id}', 'edit')->name('edit')->where(['news' => '[0-9\-]+'])->middleware(['xf.auth','can:update,news']); Route::get('/edit/news/{news:id}', 'edit')->name('edit')->where(['news' => '[0-9\-]+'])->middleware(['submissions.maintenance','xf.auth','can:update,news']);
Route::post('/edit/news/{news:id}', 'update')->name('update')->where(['news' => '[0-9\-]+'])->middleware(['xf.auth','can:update,news']); Route::post('/edit/news/{news:id}', 'update')->name('update')->where(['news' => '[0-9\-]+'])->middleware(['submissions.maintenance','xf.auth','can:update,news']);
Route::delete('/edit/news/{news:id}', 'destroy')->name('destroy')->where(['news' => '[0-9\-]+'])->middleware(['xf.auth','can:update,news']); Route::delete('/edit/news/{news:id}', 'destroy')->name('destroy')->where(['news' => '[0-9\-]+'])->middleware(['submissions.maintenance','xf.auth','can:update,news']);
}); });
// QueueController // QueueController
@@ -67,32 +67,32 @@ Route::name('queue.')->prefix('/queue')->controller(\App\Http\Controllers\QueueC
Route::get('/', 'index' )->name('index'); Route::get('/', 'index' )->name('index');
Route::patch('/{entry:id}/comment', 'updateComment' ) Route::patch('/{entry:id}/comment', 'updateComment' )
->middleware(['xf.auth', 'can:updateComment,entry' ] ) ->middleware(['submissions.maintenance','xf.auth', 'can:updateComment,entry' ] )
->where([ 'entry' => '[0-9\-]+' ]) ->where([ 'entry' => '[0-9\-]+' ])
->name('comment'); ->name('comment');
Route::patch('/{entry:id}/approve', 'approve' ) Route::patch('/{entry:id}/approve', 'approve' )
->middleware(['xf.auth', 'can:approve,entry' ] ) ->middleware(['submissions.maintenance','xf.auth', 'can:approve,entry' ] )
->where([ 'entry' => '[0-9\-]+' ]) ->where([ 'entry' => '[0-9\-]+' ])
->name('approve'); ->name('approve');
Route::patch('/{entry:id}/reject', 'reject' ) Route::patch('/{entry:id}/reject', 'reject' )
->middleware(['xf.auth', 'can:reject,entry' ] ) ->middleware(['submissions.maintenance','xf.auth', 'can:reject,entry' ] )
->where([ 'entry' => '[0-9\-]+' ]) ->where([ 'entry' => '[0-9\-]+' ])
->name('reject'); ->name('reject');
Route::patch('/news/{news:id}/comment', 'updateComment_news' ) Route::patch('/news/{news:id}/comment', 'updateComment_news' )
->middleware(['xf.auth', 'can:updateComment,news' ] ) ->middleware(['submissions.maintenance','xf.auth', 'can:updateComment,news' ] )
->where([ 'news' => '[0-9\-]+' ]) ->where([ 'news' => '[0-9\-]+' ])
->name('news.comment'); ->name('news.comment');
Route::patch('/news/{news:id}/approve', 'approve_news' ) Route::patch('/news/{news:id}/approve', 'approve_news' )
->middleware(['xf.auth', 'can:approve,news' ] ) ->middleware(['submissions.maintenance','xf.auth', 'can:approve,news' ] )
->where([ 'news' => '[0-9\-]+' ]) ->where([ 'news' => '[0-9\-]+' ])
->name('news.approve'); ->name('news.approve');
Route::patch('/news/{news:id}/reject', 'reject_news' ) Route::patch('/news/{news:id}/reject', 'reject_news' )
->middleware(['xf.auth', 'can:reject,news' ] ) ->middleware(['submissions.maintenance','xf.auth', 'can:reject,news' ] )
->where([ 'news' => '[0-9\-]+' ]) ->where([ 'news' => '[0-9\-]+' ])
->name('news.reject'); ->name('news.reject');
}); });
@@ -111,7 +111,7 @@ Route::name('tools.')->controller(\App\Http\Controllers\ToolsController::class)-
Route::name('modcp.')->prefix('/modcp')->controller(\App\Http\Controllers\ModCPController::class)->middleware(['xf.auth','can:is-mod'])->group(function () { Route::name('modcp.')->prefix('/modcp')->controller(\App\Http\Controllers\ModCPController::class)->middleware(['xf.auth','can:is-mod'])->group(function () {
Route::get('/', 'index' )->name('index'); Route::get('/', 'index' )->name('index');
Route::get('/locked-entries', 'locked' )->name('locked'); Route::get('/locked-entries', 'locked' )->name('locked')->middleware('can:moderate,\App\Models\Entry');
Route::get('/draft-entries', 'draft' )->name('draft')->middleware('can:is-admin'); Route::get('/draft-entries', 'draft' )->name('draft')->middleware('can:is-admin');
Route::get('/hidden-entries', 'hidden' )->name('hidden')->middleware('can:is-admin'); Route::get('/hidden-entries', 'hidden' )->name('hidden')->middleware('can:is-admin');
Route::get('/deleted-entries', 'deleted' )->name('deleted')->middleware('can:is-admin'); Route::get('/deleted-entries', 'deleted' )->name('deleted')->middleware('can:is-admin');
@@ -125,6 +125,8 @@ Route::name('modcp.')->prefix('/modcp')->controller(\App\Http\Controllers\ModCPC
Route::resource('authors', \App\Http\Controllers\ModCP\AuthorController::class)->only(['index', 'store','update','destroy']); Route::resource('authors', \App\Http\Controllers\ModCP\AuthorController::class)->only(['index', 'store','update','destroy']);
Route::resource('platforms', \App\Http\Controllers\ModCP\PlatformController::class )->middleware('can:is-admin')->only(['index', 'store','update','destroy']); Route::resource('platforms', \App\Http\Controllers\ModCP\PlatformController::class )->middleware('can:is-admin')->only(['index', 'store','update','destroy']);
Route::resource('genres', \App\Http\Controllers\ModCP\GenreController::class )->middleware('can:is-admin')->only(['index', 'store','update','destroy']); Route::resource('genres', \App\Http\Controllers\ModCP\GenreController::class )->middleware('can:is-admin')->only(['index', 'store','update','destroy']);
Route::resource('levels', \App\Http\Controllers\ModCP\LevelController::class )->middleware('can:is-admin')->only(['index', 'store','update','destroy']);
Route::resource('modifications', \App\Http\Controllers\ModCP\ModificationsController::class )->middleware('can:is-admin')->only(['index', 'store','update','destroy']);
}); });
// RedirectController // RedirectController

View File

@@ -23,7 +23,7 @@ function getArg(name, fallback) {
const PREFIX = getArg('prefix', '\\$' ); const PREFIX = getArg('prefix', '\\$' );
const INPUT_DIRS = getArg('input', './resources/css').split(','); const INPUT_DIRS = getArg('input', './resources/css').split(',');
const OUTPUT_FILE = getArg('output', './extra.less'); const OUTPUT_FILE = getArg('output', './extra.less');
const EXCLUDE = getArg('exclude', '*.min.css,app.css').split(','); const EXCLUDE = getArg('exclude', '*.min.css,app.css,database.css').split(',');
/** /**
* Recursively collect CSS files in a folder. * Recursively collect CSS files in a folder.