This commit is contained in:
2026-09-29 04:35:35 +02:00
parent 73c0659d4a
commit 6c813143eb
51 changed files with 3708 additions and 743 deletions
+11 -3
View File
@@ -220,7 +220,8 @@
}
#report-modal .report-modal-content * { border-radius: 0 !important; }
#report-modal .rm-header { position: relative; flex-shrink: 0; padding: 20px 64px 14px 24px; border-bottom: 1px solid var(--rm-border); }
#report-modal .rm-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 16px 24px; }
#report-modal .rm-body { flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain; padding: 16px 24px; scrollbar-width: none !important; -ms-overflow-style: none; }
#report-modal .rm-body::-webkit-scrollbar { display: none; width: 0; height: 0; }
#report-modal .rm-body > :last-child { margin-bottom: 0; }
#report-modal .rm-header h3 { margin: 0; font-size: 1.25em; font-weight: 700; color: var(--rm-text); }
#report-modal .rm-header p { margin: 6px 0 0; font-size: 0.85em; line-height: 1.5; color: var(--rm-muted); }
@@ -417,11 +418,17 @@
</div>
</div>
<div class="global-sidebar-right-footer">
@if(session)
<a href="/ranking" data-page-modal title="{{ t('footer.ranking') }}" aria-label="{{ t('footer.ranking') }}"><i class="fa-solid fa-users-between-lines"></i></a>
@endif
<a href="/rules" data-page-modal title="{{ t('footer.rules') }}" aria-label="{{ t('footer.rules') }}"><i class="fa-solid fa-scale-balanced"></i></a>
<a href="/about" data-page-modal title="{{ t('footer.about') }}" aria-label="{{ t('footer.about') }}"><i class="fa-solid fa-info"></i></a>
<a href="/privacy" data-page-modal title="Privacy" aria-label="Privacy"><i class="fa-solid fa-shield-halved"></i></a>
<div id="help-button" style="color: var(--accent); font-weight: bold; opacity: 0.7;" title="Keyboard Shortcuts" aria-label="Keyboard Shortcuts"><i class="fa-solid fa-question"></i></div>
<div id="help-button" style="color: var(--accent); font-weight: bold; opacity: 0.7;" title="Keyboard Shortcuts" aria-label="Keyboard Shortcuts"><i class="fa-solid fa-keyboard"></i></div>
@if(!/^\/\d$/.test(url.pathname))
{{-- id kept: f0ckm.js / sidebar-activity.js hook the AJAX random transition on #nav-random --}}
<a href="/random" id="nav-random" title="{{ t('nav.random') }}" aria-label="{{ t('nav.random') }}"><i class="fa-solid fa-shuffle"></i></a>
@endif
</div>
</div>
@endif
@@ -653,7 +660,8 @@
});
const json = await resp.json();
if (status) { status.textContent = json.msg || ''; status.className = json.success ? 'flash-success' : 'flash-error'; }
if (json.success) setTimeout(() => { close(registerModal); open(loginModal); }, 2000);
if (json.success && json.upgraded) setTimeout(() => { location.reload(); }, 1500);
else if (json.success) setTimeout(() => { close(registerModal); open(loginModal); }, 2000);
} catch (err) {
if (status) { status.textContent = 'Network error.'; status.className = 'flash-error'; }
} finally { btn.disabled = false; btn.textContent = 'Create Account'; }
+5
View File
@@ -347,7 +347,12 @@
#info-modal .infobox-modal-content .infobox-modal-footer { flex-shrink: 0; }
#info-modal .infobox-modal-content .infobox-modal-body {
flex: 1 1 auto; min-height: 0; max-height: none; overflow-y: auto; overscroll-behavior: contain;
/* Scrolls (wheel, touch, keyboard) but shows no scrollbar in any browser.
NOTE: no backticks or dollar-brace in views, the template engine compiles them as JS template literals */
scrollbar-width: none !important;
-ms-overflow-style: none;
}
#info-modal .infobox-modal-content .infobox-modal-body::-webkit-scrollbar { display: none; width: 0; height: 0; }
@media (max-width: 600px) {
#info-modal { padding: 0 !important; align-items: stretch !important; }
#info-modal .infobox-modal-content {
+39 -37
View File
@@ -35,9 +35,6 @@
<a href="/abyss" title="{{ t('nav.abyss') }}"><i class="fa-solid fa-dice-d6"></i></a>
@endif
<a href="#" id="nav-search-btn" title="{{ t('nav.search') }}"><i class="fa-solid fa-magnifying-glass"></i></a>
@if(!/^\/\d$/.test(url.pathname))
<a href="/random" id="nav-random" title="{{ t('nav.random') }}"><i class="fa-solid fa-shuffle"></i></a>
@endif
</div>
</div>
@@ -48,7 +45,7 @@
<div class="nav-user-dropdown">
<button class="nav-user-btn nav-avatar-btn" id="nav-user-toggle">
@if(session.is_anon)
<span class="nav-avatar-icon" style="font-size: 1.2em; margin-right: 6px; display: inline-flex; align-items: center; cursor: pointer;" onclick="event.stopPropagation(); const u = '{{ session.login.toLowerCase() }}'; if(typeof window.loadPageAjax==='function'){ window.loadPageAjax('/user/' + u, true); } else { window.location.href='/user/' + u; }" title="{{ session.login }}"><i class="fa-solid fa-user-secret"></i></span>
<img class="nav-avatar-img" src="/a/default.png" onclick="event.stopPropagation(); const u = '{{ session.login.toLowerCase() }}'; if(typeof window.loadPageAjax==='function'){ window.loadPageAjax('/user/' + u, true); } else { window.location.href='/user/' + u; }" title="{{ session.login }}">
@elseif(session.avatar_file)
<img class="nav-avatar-img" src="/a/{{ session.avatar_file }}" onerror="this.src='/a/default.png'" onclick="event.stopPropagation(); if(typeof window.loadPageAjax==='function'){ window.loadPageAjax('/user/{!! session.user.toLowerCase() !!}', true); } else { window.location.href='/user/{!! session.user.toLowerCase() !!}'; }" title="{{ session.user }}">
@elseif(session.avatar && session.avatar > 0)
@@ -68,12 +65,12 @@
<a href="/user/{!! session.user.toLowerCase() !!}/halls">{{ t('nav.my_halls') }}</a>
@endif
@endif
<a href="/pending">Pending Uploads</a>
<a href="/pending" class="nav-menu-pending">Pending@if(typeof session.own_pending_count === 'number')<span class="nav-menu-count @if(session.own_pending_count === 0)is-zero@endif" title="{{ session.own_pending_count }} upload(s) awaiting approval">{{ session.own_pending_count }}</span>@endif</a>
@if(enable_anonymous_access && session.is_anon)
<a href="#" id="nav-user-anon-identity-btn" style="white-space: nowrap;" onclick="event.preventDefault(); if(window.f0ckAnonPasskey) { window.f0ckAnonPasskey.openModal(); } else { const m = document.getElementById('anon-passkey-modal'); if (m) m.style.display='flex'; }">Passkey Identity</a>
@endif
<a href="/user/{{ (session.is_anon && session.login ? session.login : session.user).toLowerCase() }}/favs" class="mobile-only">{{ t('nav.favs') }}</a>
<a href="/settings" class="mobile-only">{{ t('nav.settings') }}</a>
<a href="/settings" class="mobile-only" data-page-modal title="{{ t('nav.settings') }}">{{ t('nav.settings') }}</a>
<div class="nav-user-divider"></div>
<a href="/logout" class="mobile-only">{{ t('nav.logout') }}</a>
</div>
@@ -94,6 +91,9 @@
@else
<button class="notif-tab active" data-tab="system">{{ t('nav.notif_tab_system') }} <span class="notif-tab-badge" id="notif-tab-badge-system" style="display:none">0</span></button>
@endif
@if(session && session.admin)
<button class="notif-tab" data-tab="admin">Admin <span class="notif-tab-badge" id="notif-tab-badge-admin" style="display:none">0</span></button>
@endif
</div>
<button id="mark-all-read" title="{{ t('nav.mark_all_read') }}"><i class="fa-solid fa-check-double"></i></button>
</div>
@@ -129,7 +129,7 @@
@endif
<!-- Quick Settings -->
<a href="/settings" title="Settings" class="desktop-only"><i class="fa-solid fa-gear"></i></a>
<a href="/settings" title="Settings" class="desktop-only" data-page-modal><i class="fa-solid fa-gear"></i></a>
<!-- Filter -->
@if(!session.is_anon || (enable_anonymous_access && anon_permissions.filter))
@@ -171,9 +171,9 @@
</nav>
@if(session.admin || session._is_impersonating)
@if(session.admin || session._is_impersonating || session._preview_role)
<!-- Admin Bar (Bottom, hotkey-activated) -->
<div class="admin-bar" id="admin-bar">
<div class="admin-bar" id="admin-bar" data-preview-role="{{ session._preview_role || 'admin' }}">
@if(session._is_impersonating)
<!-- Impersonation Banner -->
<div class="admin-bar-impersonate-notice">
@@ -190,10 +190,6 @@
@else
<!-- Normal Admin Bar -->
<div class="admin-bar-inner">
<div class="admin-bar-brand">
<i class="fa-solid fa-shield-halved"></i>
<span>Admin</span>
</div>
<div class="admin-bar-links">
<a href="/admin" class="admin-bar-link" title="Admin panel">
@@ -235,7 +231,7 @@
@endif
<!-- Floating exit-preview pill (visible only during role preview) -->
@if(session.admin)
@if(session.admin || session._preview_role)
<div id="admin-preview-exit-pill" style="display:none;">
<i class="fa-solid fa-eye"></i>
<span id="admin-preview-exit-label">Previewing as User</span>
@@ -279,9 +275,6 @@
@if(abyss_enabled)
<a href="/abyss" title="Abyss"><i class="fa-solid fa-dice-d6"></i></a>
@endif
@if(!/^\/\d$/.test(url.pathname))
<a href="/random" id="nav-random" title="Random"><i class="fa-solid fa-shuffle"></i></a>
@endif
<a href="#" id="nav-search-btn" title="Search"><i class="fa-solid fa-magnifying-glass"></i></a>
</div>
@@ -301,7 +294,7 @@
@if(enable_anonymous_access)
<a href="#" id="nav-anon-identity-btn" @if(!session || !session.is_anon) style="display:none;" @endif onclick="event.preventDefault(); if(window.f0ckAnonPasskey) { window.f0ckAnonPasskey.openModal(); } else { const m = document.getElementById('anon-passkey-modal'); if (m) m.style.display='flex'; }"><i class="fa-solid fa-fingerprint"></i> Passkey Identity</a>
<a href="/settings" id="nav-anon-settings-btn" @if(!session || !session.is_anon) style="display:none;" @endif><i class="fa-solid fa-gear"></i> Settings</a>
<a href="/settings" id="nav-anon-settings-btn" data-page-modal title="Settings" @if(!session || !session.is_anon) style="display:none;" @endif><i class="fa-solid fa-gear"></i> Settings</a>
@endif
<a href="#" id="nav-login-btn">{{ t('nav.login') }}</a>
@if(registration_open)
@@ -1082,6 +1075,9 @@ async function loginWithPasskey() {
<div class="shortcut-item"><span><kbd>Ctrl</kbd> + <kbd>Enter</kbd></span><span>{{ t('shortcuts.send_comment') }}</span></div>
<div class="shortcut-item"><span><kbd>s</kbd></span><span>{{ t('shortcuts.flash_yank') }}</span></div>
@if(session)
<div class="shortcut-item"><span><kbd>Shift</kbd> + <kbd>S</kbd></span><span>{{ t('shortcuts.open_settings') }}</span></div>
@endif
@if(session)
<div class="shortcut-item"><span><kbd>e</kbd></span><span>{{ t('shortcuts.tag_exclude') }}</span></div>
<div class="shortcut-item"><span><kbd>i</kbd></span><span>{{ t('shortcuts.tag_input') }}</span></div>
@endif
@@ -1241,8 +1237,8 @@ async function loginWithPasskey() {
<button class="modal-close" id="drag-modal-close" title="Cancel Upload">&times;</button>
<div class="modal-body">
<div class="upload-container" style="padding: 0; animation: none; opacity: 1;">
<div class="upload-title">{{ t('upload.title') }}</div>
<div class="upload-title">
<span class="upload-title-text">{{ t('upload.title') }}</span>
<div class="upload-limit-info">
@if(session.uploads_remaining === undefined)
<span class="limit-unlimited"><i class="fa-solid fa-infinity"></i> {{ t('upload.unlimited') }}</span>
@@ -1252,6 +1248,7 @@ async function loginWithPasskey() {
<span class="limit-remaining"><i class="fa-solid fa-upload"></i> {{ session.uploads_remaining }}/{{ upload_limit }} {{ t('upload.remaining') }}</span>
@endif
</div>
</div>
@include(snippets/upload-form)
</div>
@@ -1261,7 +1258,7 @@ async function loginWithPasskey() {
@endif
@if(recaptcha_enabled && !session)
@if(recaptcha_enabled && (!session || session.is_anon))
<script>
(function() {
var _rcModalWidgetId = null;
@@ -1300,7 +1297,7 @@ async function loginWithPasskey() {
<script src="https://www.google.com/recaptcha/api.js?onload=onRecaptchaModalReady&render=explicit" async defer></script>
@endif
@if(session && (session.admin || session._is_impersonating))
@if(session && (session.admin || session._is_impersonating || session._preview_role))
<style>
/* ── Admin Bar ──────────────────────────────────────────────────────────────── */
.admin-bar {
@@ -1730,7 +1727,8 @@ body.preview-as-user .admin-delete-btn,
body.preview-as-user .admin-edit-btn,
body.preview-as-user .admin-pin-btn,
body.preview-as-user .delete-btn,
body.preview-as-user .poll-delete-btn,
body.preview-as-user .poll-delete-btn { display: none !important; }
/* Title stays visible but loses its click-to-edit */
body.preview-as-user .item_title_text { display: inline !important; cursor: default !important; pointer-events: none !important; }
body.preview-as-user .item_title_text.no-title { display: none !important; }
@@ -2012,8 +2010,10 @@ body.preview-as-mod .btn-infobox-action { display: none !important; }
}
/* ── Admin bar: Preview As role toggle ──────────────────────────────────── */
// The role is applied server-side (cookie preview_role, see src/index.mjs): templates, f0ckSession and
// admin/mod-guarded routes all behave as that role. Switching sets the cookie and reloads.
(function() {
const STORAGE_KEY = 'f0ck_preview_role';
const COOKIE = 'preview_role';
const pills = document.querySelectorAll('.admin-bar-preview-pill');
const exitPill = document.getElementById('admin-preview-exit-pill');
const exitLabel = document.getElementById('admin-preview-exit-label');
@@ -2040,26 +2040,28 @@ body.preview-as-mod .btn-infobox-action { display: none !important; }
}
}
// Init from localStorage
const saved = localStorage.getItem(STORAGE_KEY) || 'admin';
applyRole(saved);
function switchRole(role) {
const secure = location.protocol === 'https:' ? '; Secure' : '';
// No template literals here: the view engine would evaluate the placeholders server-side
document.cookie = role === 'admin'
? COOKIE + '=; path=/; max-age=0; SameSite=Lax' + secure
: COOKIE + '=' + role + '; path=/; max-age=86400; SameSite=Lax' + secure;
location.reload();
}
// The server tells which role this page was rendered as
try { localStorage.removeItem('f0ck_preview_role'); } catch {} // old client-only preview
const current = (adminBar && adminBar.dataset.previewRole) || 'admin';
applyRole(roleLabels[current] ? current : 'admin');
// Pill click
pills.forEach(pill => {
pill.addEventListener('click', () => {
const role = pill.dataset.role;
localStorage.setItem(STORAGE_KEY, role);
applyRole(role);
if (role !== current) switchRole(role);
});
});
// Exit button
if (exitBtn) {
exitBtn.addEventListener('click', () => {
localStorage.setItem(STORAGE_KEY, 'admin');
applyRole('admin');
});
}
if (exitBtn) exitBtn.addEventListener('click', () => switchRole('admin'));
})();
})();
+10 -10
View File
@@ -1,6 +1,6 @@
@each(notifications as n)
@if(n.type === 'approve')
<a href="/{{ n.item_slug || n.item_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<a href="/{{ n.item_slug || n.item_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-type="{{ n.type }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/pending/t/{{ n.item_id }}.webp';this.onerror=function(){this.onerror=null;this.src='/mod/deleted/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none';};};"/>
</div>
@@ -11,7 +11,7 @@
</div>
</a>
@elseif(n.type === 'admin_pending')
<a href="/mod/approve" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<a href="/mod/approve" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-type="{{ n.type }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none';};"/>
</div>
@@ -22,7 +22,7 @@
</div>
</a>
@elseif(n.type === 'report')
<a href="/mod/reports" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<a href="/mod/reports" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-type="{{ n.type }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/pending/t/{{ n.item_id }}.webp';this.onerror=function(){this.onerror=null;this.src='/mod/deleted/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none';};};"/>
</div>
@@ -33,7 +33,7 @@
</div>
</a>
@elseif(n.type === 'deny')
<a href="/{{ n.item_slug || n.item_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<a href="/{{ n.item_slug || n.item_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-type="{{ n.type }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none'};" />
</div>
@@ -47,7 +47,7 @@
</div>
</a>
@elseif(n.type === 'item_deleted')
<a href="/{{ n.item_slug || n.item_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<a href="/{{ n.item_slug || n.item_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-type="{{ n.type }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none'};" />
</div>
@@ -61,7 +61,7 @@
</div>
</a>
@elseif(n.type === 'upload_comment')
<a href="/{{ n.item_slug || n.item_id }}#c{{ n.reference_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<a href="/{{ n.item_slug || n.item_id }}#c{{ n.reference_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-type="{{ n.type }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumbnail" onerror="this.style.display='none'">
</div>
@@ -72,7 +72,7 @@
</div>
</a>
@elseif(n.type === 'upload_success')
<a href="/{{ n.item_slug || n.item_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<a href="/{{ n.item_slug || n.item_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-type="{{ n.type }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.style.display='none'" />
</div>
@@ -83,7 +83,7 @@
</div>
</a>
@elseif(n.type === 'upload_error')
<a href="{{ n.item_id ? '/' + (n.item_slug || n.item_id) : '#' }}" class="notif-item {{ n.is_read ? '' : 'unread' }} {{ n.item_id ? 'notif-with-thumb' : '' }}" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<a href="{{ n.item_id ? '/' + (n.item_slug || n.item_id) : '#' }}" class="notif-item {{ n.is_read ? '' : 'unread' }} {{ n.item_id ? 'notif-with-thumb' : '' }}" data-id="{{ n.id }}" data-type="{{ n.type }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
@if(n.item_id)
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.onerror=null;this.src='/pending/t/{{ n.item_id }}.webp';this.onerror=function(){this.style.display='none'};" />
@@ -104,7 +104,7 @@
</div>
</a>
@elseif(n.type === 'warning')
<div class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<div class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-type="{{ n.type }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<div class="notif-thumb" style="display:flex;align-items:center;justify-content:center;background:var(--bg-lighter);color:var(--danger);font-size:1.5em;"><i class="fa-solid fa-triangle-exclamation"></i></div>
<div class="notif-content">
<div class="notif-user"><strong>{{ t('notifications.system') }}</strong></div>
@@ -116,7 +116,7 @@
</div>
</div>
@else
<a href="/{{ n.item_slug || n.item_id }}#c{{ n.comment_id || n.reference_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
<a href="/{{ n.item_slug || n.item_id }}#c{{ n.comment_id || n.reference_id }}" class="notif-item {{ n.is_read ? '' : 'unread' }} notif-with-thumb" data-id="{{ n.id }}" data-type="{{ n.type }}" data-item-id="{{ n.item_id || '' }}" data-item-slug="{{ n.item_slug || '' }}">
@if(n.item_id)
<div class="notif-thumb" data-mode="{{ n.item_mode || '' }}">
<img src="/t/{{ n.item_id }}{{ ((active_mode===0&&(n.item_mode==='nsfw'||n.item_mode==='nsfl'))||(active_mode===1&&n.item_mode==='nsfl')||(active_mode===4&&(n.item_mode==='sfw'||n.item_mode==='nsfw'))) ? '_blur' : '' }}.webp" data-orig-src="/t/{{ n.item_id }}.webp" alt="thumb" onerror="this.style.display='none'" />
+19 -1
View File
@@ -6,7 +6,7 @@
<button type="button" class="pgm-close" title="Close" aria-label="Close">&times;</button>
</div>
</div>
<div class="pgm-body"></div>
<div class="pgm-body" tabindex="-1"></div>
</div>
<style>
#page-modal {
@@ -17,10 +17,21 @@
flex-direction: column;
background: var(--bg); color: var(--text-color, #fff);
}
/* The Onara viewer (10050) lifts the sidebar (10060) and its drag zone (10059) above itself; keep the same
order: page over Onara, still under the sidebar controls and the navbar (10070) */
body.onara-modal-open #page-modal { z-index: 10055; }
#page-modal .pgm-header {
flex-shrink: 0; display: flex; align-items: center; justify-content: space-between; gap: 12px;
padding: 12px 20px; border-bottom: 1px solid var(--accent);
}
/* Height matched to the sidebar tab row (page-modal.js measures it); the close button scales with it */
#page-modal.pgm-header-synced .pgm-header {
box-sizing: border-box; height: var(--pgm-header-h); padding-top: 0; padding-bottom: 0;
}
#page-modal.pgm-header-synced .pgm-close {
width: calc(var(--pgm-header-h) - 12px); height: calc(var(--pgm-header-h) - 12px);
font-size: 1.3em;
}
#page-modal .pgm-title {
margin: 0; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
font-size: 1em; font-weight: 800; text-transform: uppercase; letter-spacing: 0.08em;
@@ -37,7 +48,14 @@
#page-modal .pgm-body {
flex: 1 1 auto; min-height: 0; overflow-y: auto; overscroll-behavior: contain;
padding-bottom: env(safe-area-inset-bottom, 0px);
/* Scrollbar hidden, scrolling kept (wheel, touch, keyboard). Firefox only honours scrollbar-width;
!important so a site-wide scrollbar style can't bring it back */
scrollbar-width: none !important;
-ms-overflow-style: none;
}
#page-modal .pgm-body::-webkit-scrollbar { display: none; width: 0; height: 0; }
/* Focusable so arrow keys / PageUp / PageDown / Space scroll it once clicked into, without an outline */
#page-modal .pgm-body:focus { outline: none; }
#page-modal .pgm-loading { padding: 60px 20px; text-align: center; color: var(--text-muted, #aaa); }
#page-modal .pgm-loading a { color: var(--accent); }
</style>
+5 -5
View File
@@ -22,11 +22,7 @@
<div class="drop-zone" id="upload-form-drop-zone">
<input type="file" class="file-input" name="file" accept="{{ allowed_mimes }}" multiple>
<div class="drop-zone-prompt">
<p class="drop-zone-title" style="font-size: 1.1rem; font-weight: 500;">{{ t('upload.drop_here') }}</p>
<p style="font-size: 0.9rem; opacity: 0.6;">(max {{ max_file_size }})@if(session.admin) <span style="color: var(--accent);">{{ t('upload.admin_boost') }}</span>@endif</p>
<p class="drop-album-hint" style="margin-top: 6px; font-size: 0.85rem; color: var(--accent); opacity: 0.95;">
<i class="fa-solid fa-layer-group"></i> {{ t('album.drop_hint') || 'Select or drop multiple files (subf0cks) to create an Album' }}
</p>
<p class="drop-zone-title" style="font-size: 1.1rem; font-weight: 500;">{{ t('upload.drop_here') }} <span class="drop-zone-max" style="font-size: 0.8em; font-weight: 400; opacity: 0.6;">(max {{ max_file_size }})</span>@if(session.admin) <span style="font-size: 0.8em; font-weight: 400; color: var(--accent);">{{ t('upload.admin_boost') }}</span>@endif</p>
</div>
<!-- Preview Container -->
<div class="file-preview" style="display: none;">
@@ -180,6 +176,9 @@
</label>
</div>
<!-- Footer: upload button, progress and status. Pinned to the bottom of the upload dropdown
while the form above it scrolls (f0ckm.css #upload-drag-modal .upload-footer) -->
<div class="upload-footer">
<div class="form-actions">
<button type="submit" class="btn-upload" disabled>
<span class="btn-text">{{ t('upload.select_file') }}</span>
@@ -195,4 +194,5 @@
</div>
<div class="upload-status"></div>
</div>
</form>