From b422cd2c82d7ec8c73924c9e8c9eca3cf32dcd53 Mon Sep 17 00:00:00 2001 From: Benjamin Date: Wed, 1 Jul 2026 11:51:30 +0200 Subject: [PATCH] Fixed a lot of responsive problems. - Fixed auth problem - Fixed BBCode and Markdown apparition in some unnecessary parts --- app/Auth/XenForoGuard.php | 71 +++++++++++++++++ app/Helpers/EntryHelpers.php | 14 ++++ app/Services/ActivityService.php | 11 +-- resources/css/components/cards.css | 10 ++- resources/css/components/common.css | 39 ++++++++++ resources/css/components/database.css | 53 +++++++++++-- resources/css/components/forms.css | 78 ++++++++++++++++++- resources/css/components/grid.css | 32 +++++++- resources/css/layout/activity.css | 10 ++- resources/css/layout/entry.css | 1 + resources/css/layout/menu.css | 11 ++- resources/css/layout/responsive.css | 3 +- .../js/SubmissionsClass/GalleryManager.js | 29 ++++++- resources/js/app.js | 2 + resources/js/hovercard.js | 22 +++++- resources/js/mobile-back-to-top.js | 18 +++++ .../views/components/gallery-field.blade.php | 18 ++++- .../components/staff-credits-field.blade.php | 6 +- resources/views/entries/comments.blade.php | 2 +- resources/views/layouts/app.blade.php | 5 ++ resources/views/layouts/menu.blade.php | 6 +- 21 files changed, 403 insertions(+), 38 deletions(-) create mode 100644 resources/js/mobile-back-to-top.js diff --git a/app/Auth/XenForoGuard.php b/app/Auth/XenForoGuard.php index 30636cd..fa49413 100644 --- a/app/Auth/XenForoGuard.php +++ b/app/Auth/XenForoGuard.php @@ -64,6 +64,19 @@ class XenForoGuard implements Guard if ($this->hasUser()) 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'); if(!$sessionId) return null; @@ -92,6 +105,64 @@ class XenForoGuard implements Guard 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. * diff --git a/app/Helpers/EntryHelpers.php b/app/Helpers/EntryHelpers.php index 928bb6a..d714d5b 100644 --- a/app/Helpers/EntryHelpers.php +++ b/app/Helpers/EntryHelpers.php @@ -125,4 +125,18 @@ class EntryHelpers { session(["downloaded_file_{$entryFile->file_uuid}" => 1]); 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'); + } } diff --git a/app/Services/ActivityService.php b/app/Services/ActivityService.php index 7a1099e..6c91653 100644 --- a/app/Services/ActivityService.php +++ b/app/Services/ActivityService.php @@ -2,6 +2,7 @@ namespace App\Services; +use App\Helpers\EntryHelpers; use App\Models\Entry; use App\Models\EntryReview; use App\Models\News; @@ -65,7 +66,7 @@ class ActivityService 'user_id' => $entry->user_id, 'badge' => EntryCard::ENTRY_TYPES_BADGE[$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 ]; } @@ -82,7 +83,7 @@ class ActivityService 'user_id' => $news->user_id, 'badge' => '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 ]; } @@ -99,7 +100,7 @@ class ActivityService 'user_id' => $message->user_id, 'badge' => 'Post', '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 ]; @@ -117,7 +118,7 @@ class ActivityService 'user_id' => $thread->user_id, 'badge' => '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 ]; @@ -152,7 +153,7 @@ class ActivityService 'user_id' => $review->user_id, 'badge' => '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, ]; } diff --git a/resources/css/components/cards.css b/resources/css/components/cards.css index ca2a285..f133400 100644 --- a/resources/css/components/cards.css +++ b/resources/css/components/cards.css @@ -30,6 +30,7 @@ border: 1px solid var(--border); display: flex; min-width: 0; + max-width: 100%; flex-direction: column; transition: transform 0.2s, border-color 0.2s; cursor: pointer; @@ -72,6 +73,7 @@ .entry-card-info { padding: 15px; flex-grow: 1; + min-width: 0; display: flex; flex-direction: column; } @@ -82,13 +84,16 @@ font-size: 1.1rem; margin-bottom: 5px; line-height: 1.3; - word-wrap: break-word; + overflow-wrap: anywhere; + word-break: break-word; } .entry-card-author { color: var(--rhpz-orange); font-size: 0.85rem; margin-bottom: 10px; + overflow-wrap: anywhere; + word-break: break-word; } .entry-card-meta { @@ -148,6 +153,9 @@ .entry-card-meta { font-size: 0.75rem; + flex-direction: column; + align-items: flex-start; + gap: 6px; } } diff --git a/resources/css/components/common.css b/resources/css/components/common.css index 9c1ca50..ccba470 100644 --- a/resources/css/components/common.css +++ b/resources/css/components/common.css @@ -43,6 +43,45 @@ 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 { diff --git a/resources/css/components/database.css b/resources/css/components/database.css index 905cb07..32bcf99 100644 --- a/resources/css/components/database.css +++ b/resources/css/components/database.css @@ -227,8 +227,10 @@ display: flex; justify-content: center; align-items: center; + flex-wrap: wrap; gap: 4px; margin-top: 20px; + width: 100%; .btn { min-width: 36px; @@ -255,18 +257,20 @@ } @media (max-width: 900px) { - .database-layout { - flex-direction: column; - } - .database-wrapper { flex-direction: column; + width: 100%; } .database-filters { width: 100%; 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 { @@ -286,13 +290,32 @@ 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 { flex-direction: column; gap: 15px; + width: 100%; } .database-filters { - width: 100%; + width: min(100%, 420px); + max-width: 420px; + margin: 0 auto; grid-template-columns: 1fr; order: -1; margin-bottom: 10px; @@ -303,7 +326,7 @@ } .grid-entries { - grid-template-columns: repeat(3, 1fr); + grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 15px; } } @@ -311,6 +334,20 @@ @media (max-width: 600px) { .database-search { 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 { @@ -318,7 +355,7 @@ } .grid-entries { - grid-template-columns: repeat(2, 1fr); + grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 12px; } diff --git a/resources/css/components/forms.css b/resources/css/components/forms.css index 4f75308..fcc6ecd 100644 --- a/resources/css/components/forms.css +++ b/resources/css/components/forms.css @@ -393,6 +393,37 @@ min-width: 20px; 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 { display: grid; grid-template-columns: repeat(4, 1fr); @@ -510,13 +541,30 @@ } @media (max-width: 600px) { - .upload-item-actions { + .upload-item { 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; + width: 100%; + margin-top: 8px; + flex-basis: 100%; } .upload-item-actions .btn { - width: 100%; + width: auto; + flex: 0 0 auto; } } .file-state-icon { width: 18px; height: 18px; } @@ -673,10 +721,18 @@ .grid-hashes { grid-template-columns: 1fr; } - + .grid-credits { + grid-template-columns: 1fr; + } + .grid-first { + display: none; + } .hash-first { display: none; } + .author-search-selected { + display: none; + } } @media (max-width: 600px) { @@ -706,4 +762,20 @@ .form-error-text { 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; + } + } } diff --git a/resources/css/components/grid.css b/resources/css/components/grid.css index 646de42..48e1dcd 100644 --- a/resources/css/components/grid.css +++ b/resources/css/components/grid.css @@ -30,8 +30,38 @@ .grid-entries { display: grid; - grid-template-columns: repeat(6,1fr); + grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 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; + } +} + diff --git a/resources/css/layout/activity.css b/resources/css/layout/activity.css index 54d7b07..3fe6a9d 100644 --- a/resources/css/layout/activity.css +++ b/resources/css/layout/activity.css @@ -223,7 +223,7 @@ .activity-tl-card-description { font-size: 0.8rem; color: var(--text2); - white-space: nowrap; + word-break: break-word; text-overflow: ellipsis; line-height: 1.3; } @@ -265,13 +265,15 @@ @media (max-width: 600px) { .activity-tl-header { flex-direction: column; align-items: flex-start; } .activity-tl-thumb { display: none; } + .activity-tl-left { display: none; } + .activity-tl-card-description { display: none; } .activity-day-sep { padding-left: 44px; } .activity-tl-left { width: 44px; } - + .activity-tl-date { font-size: 0.75rem; } - + .activity-tl-content-title { font-size: 0.9rem; } @@ -279,7 +281,7 @@ @media (max-width: 768px) { .activity-timeline { - padding-left: 50px; + padding-left: 0px; } .activity-tl-left { diff --git a/resources/css/layout/entry.css b/resources/css/layout/entry.css index efe1d7f..021fa0a 100644 --- a/resources/css/layout/entry.css +++ b/resources/css/layout/entry.css @@ -101,6 +101,7 @@ line-height: 1.6; color: var(--text); margin-bottom: 30px; + word-break: break-word; } .entry-gallery { diff --git a/resources/css/layout/menu.css b/resources/css/layout/menu.css index cf88382..138e0c7 100644 --- a/resources/css/layout/menu.css +++ b/resources/css/layout/menu.css @@ -7,6 +7,7 @@ flex-shrink: 0; transition: transform 0.3s ease; z-index: 100; + overflow: hidden; .menu-header { padding: 10px; @@ -44,9 +45,12 @@ } .menu-navigation { - flex-grow: 1; + flex: 1 1 auto; + min-height: 0; padding: 10px 0; overflow-y: auto; + -webkit-overflow-scrolling: touch; + overscroll-behavior: contain; .menu-group { margin-bottom: 20px; @@ -96,7 +100,9 @@ } .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); display: flex; align-items: center; @@ -131,6 +137,7 @@ &.username { font-size: 0.9rem; font-weight: 600; + text-decoration: none !important; } &.user_role { font-size: 0.75rem; diff --git a/resources/css/layout/responsive.css b/resources/css/layout/responsive.css index 56e3287..8af224f 100644 --- a/resources/css/layout/responsive.css +++ b/resources/css/layout/responsive.css @@ -8,7 +8,8 @@ position: fixed; left: 0; top: 60px; - height: calc(100vh - 60px); + height: calc(100dvh - 60px); + max-height: calc(100dvh - 60px); transform: translateX(-100%); transition: transform 0.3s ease-in-out; z-index: 999; diff --git a/resources/js/SubmissionsClass/GalleryManager.js b/resources/js/SubmissionsClass/GalleryManager.js index 3cd11e6..2f8dadc 100644 --- a/resources/js/SubmissionsClass/GalleryManager.js +++ b/resources/js/SubmissionsClass/GalleryManager.js @@ -131,15 +131,38 @@ export function GalleryManager() { 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){ e.preventDefault(); if( this.dragSrcI === null || this.dragSrcI === index ) return; - const moved = this.images.splice(this.dragSrcI, 1)[0]; - this.images.splice(index, 0, moved); - this.dragSrcI = index; + this.reorderImages(this.dragSrcI, index); }, dragEnd(){ diff --git a/resources/js/app.js b/resources/js/app.js index 8fc69c6..fba93ce 100644 --- a/resources/js/app.js +++ b/resources/js/app.js @@ -8,6 +8,7 @@ import notifications from "./notifications.js"; import conversations from "./conversations.js"; import settings from "./settings.js"; import { initMobileMenu } from "./mobile-menu.js"; +import { initMobileBackToTop } from "./mobile-back-to-top.js" /** * Get config defined in meta.blade.php @@ -47,3 +48,4 @@ Alpine.store('settings', settings() ); // Mobile Menu document.addEventListener('DOMContentLoaded', initMobileMenu); +document.addEventListener( 'DOMContentLoaded', initMobileBackToTop ); diff --git a/resources/js/hovercard.js b/resources/js/hovercard.js index a66bd8d..1cdbaca 100644 --- a/resources/js/hovercard.js +++ b/resources/js/hovercard.js @@ -72,6 +72,7 @@ export default function hovercard(){ Alpine.nextTick(() => { const card = document.querySelector('.hovercard'); if (card) window.refreshIcons(card); + this.updatePosition(this.anchorEl); }); } catch( error ){ @@ -89,13 +90,26 @@ export default function hovercard(){ const RECT = anchorEl.getBoundingClientRect(); const SCROLL_X = window.scrollX; 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; - const WIDTH = 280; - if( x + WIDTH > window.innerWidth ){ - x = window.innerWidth - WIDTH - 16; + if( x + WIDTH > VIEWPORT_WIDTH - 8 && RECT.left + SCROLL_X - WIDTH - 8 >= 8 ){ + x = RECT.left + SCROLL_X - WIDTH - 8; + } 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; diff --git a/resources/js/mobile-back-to-top.js b/resources/js/mobile-back-to-top.js new file mode 100644 index 0000000..ac15062 --- /dev/null +++ b/resources/js/mobile-back-to-top.js @@ -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 }); +} diff --git a/resources/views/components/gallery-field.blade.php b/resources/views/components/gallery-field.blade.php index c1e5a8f..b0f5057 100644 --- a/resources/views/components/gallery-field.blade.php +++ b/resources/views/components/gallery-field.blade.php @@ -15,7 +15,15 @@ diff --git a/resources/views/components/staff-credits-field.blade.php b/resources/views/components/staff-credits-field.blade.php index ae7930a..6433049 100644 --- a/resources/views/components/staff-credits-field.blade.php +++ b/resources/views/components/staff-credits-field.blade.php @@ -2,9 +2,9 @@