ghost rider
This commit is contained in:
@@ -74,6 +74,9 @@
|
||||
<input type="hidden" id="report-comment-id">
|
||||
<input type="hidden" id="report-user-id">
|
||||
<textarea id="report-reason" class="mod-reason" placeholder="{{ t('report.placeholder') }}"></textarea>
|
||||
@if(recaptcha_enabled && !session)
|
||||
<div id="modal-report-recaptcha" data-sitekey="{{ recaptcha_site_key }}" style="margin: 10px 0; display: flex; justify-content: center;"></div>
|
||||
@endif
|
||||
<div id="report-error" class="error-msg"></div>
|
||||
<div class="modal-actions">
|
||||
<button id="report-submit" class="btn-danger">{{ t('report.submit') }}</button>
|
||||
@@ -496,6 +499,8 @@
|
||||
reason_required: "{{ t('toast.reason_required') }}",
|
||||
reason_optional: "{{ t('toast.reason_optional') }}",
|
||||
reason_required_label: "{{ t('toast.reason_required_label') }}",
|
||||
captcha_required: "{{ t('toast.captcha_required') }}",
|
||||
captcha_loading: "{{ t('toast.captcha_loading') }}",
|
||||
confirm_yes: "{{ t('toast.yes') }}",
|
||||
confirm_no: "{{ t('toast.no') }}",
|
||||
confirm_btn: "{{ t('toast.confirm') }}",
|
||||
|
||||
+266
-71
@@ -58,20 +58,6 @@
|
||||
<a href="/user/{!! session.user.toLowerCase() !!}/halls">{{ t('nav.my_halls') }}</a>
|
||||
@endif
|
||||
<a href="/user/{{ session.user.toLowerCase() }}/favs" class="mobile-only">{{ t('nav.favs') }}</a>
|
||||
@if(session.admin)
|
||||
<a href="/admin">{{ t('nav.admin') }}
|
||||
@if(typeof session.pending_count !== 'undefined' && session.pending_count > 0)
|
||||
<span class="notification-dot" title="{{ session.pending_count }} Pending" onclick="event.preventDefault(); window.location.href='/admin/approve';"></span>
|
||||
@endif
|
||||
</a>
|
||||
@endif
|
||||
@if(session.admin || session.is_moderator)
|
||||
<a href="/mod">{{ t('nav.mod') }}
|
||||
@if(typeof session.pending_count !== 'undefined' && session.pending_count > 0)
|
||||
<span class="notification-dot" title="{{ session.pending_count }} Pending" onclick="event.preventDefault(); window.location.href='/mod/approve';"></span>
|
||||
@endif
|
||||
</a>
|
||||
@endif
|
||||
<a href="/settings" class="mobile-only">{{ t('nav.settings') }}</a>
|
||||
<div class="nav-user-divider"></div>
|
||||
<a href="/logout" class="mobile-only">{{ t('nav.logout') }}</a>
|
||||
@@ -156,63 +142,70 @@
|
||||
<div id="user-pref-show-motd" style="display:none">@if(session.show_motd !== false)true@else false @endif</div>
|
||||
</div>
|
||||
|
||||
@if(session.admin || session._is_impersonating)
|
||||
<!-- Admin Bar -->
|
||||
<div class="admin-bar" id="admin-bar">
|
||||
@if(session._is_impersonating)
|
||||
<!-- Impersonation Banner -->
|
||||
<div class="admin-bar-impersonate-notice">
|
||||
<span class="admin-bar-imp-icon"><i class="fa-solid fa-user-secret"></i></span>
|
||||
<span class="admin-bar-imp-text">Viewing as <strong>{!! session.user !!}</strong></span>
|
||||
<span class="admin-bar-imp-hint">— all actions are performed as this user</span>
|
||||
<button class="admin-bar-exit-btn" id="admin-bar-exit-impersonate" title="Exit impersonation and return to your admin session">
|
||||
<i class="fa-solid fa-door-open"></i> Exit
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
@if(session.admin || session._is_impersonating)
|
||||
<!-- Admin Bar (Bottom, hotkey-activated) -->
|
||||
<div class="admin-bar" id="admin-bar">
|
||||
@if(session._is_impersonating)
|
||||
<!-- Impersonation Banner -->
|
||||
<div class="admin-bar-impersonate-notice">
|
||||
<span class="admin-bar-imp-icon"><i class="fa-solid fa-user-secret"></i></span>
|
||||
<span class="admin-bar-imp-text">Viewing as <strong>{!! session.user !!}</strong></span>
|
||||
<span class="admin-bar-imp-hint">— all actions are performed as this user</span>
|
||||
<button class="admin-bar-exit-btn" id="admin-bar-exit-impersonate" title="Exit impersonation and return to your admin session">
|
||||
<i class="fa-solid fa-door-open"></i> Exit
|
||||
</button>
|
||||
<button class="admin-bar-close-btn" id="admin-bar-close-imp" title="Close admin bar (^ or ~)" aria-label="Close admin bar" style="margin-left: 8px;">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
@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>
|
||||
@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">
|
||||
<i class="fa-solid fa-cog"></i> Admin
|
||||
@if(typeof session.pending_count !== 'undefined' && session.pending_count > 0)
|
||||
<span class="admin-bar-badge">{{ session.pending_count }}</span>
|
||||
@endif
|
||||
</a>
|
||||
<a href="/admin/approve" class="admin-bar-link" title="Pending approvals">
|
||||
<i class="fa-solid fa-clock"></i> Pending
|
||||
@if(typeof session.pending_count !== 'undefined' && session.pending_count > 0)
|
||||
<span class="admin-bar-badge">{{ session.pending_count }}</span>
|
||||
@endif
|
||||
</a>
|
||||
<a href="/mod" class="admin-bar-link" title="Moderator panel">
|
||||
<i class="fa-solid fa-gavel"></i> Mod
|
||||
</a>
|
||||
<a href="/admin/audit" class="admin-bar-link" title="Audit log">
|
||||
<i class="fa-solid fa-scroll"></i> Audit
|
||||
</a>
|
||||
</div>
|
||||
<div class="admin-bar-links">
|
||||
<a href="/admin" class="admin-bar-link" title="Admin panel">
|
||||
<i class="fa-solid fa-cog"></i> Admin
|
||||
@if(typeof session.pending_count !== 'undefined' && session.pending_count > 0)
|
||||
<span class="admin-bar-badge">{{ session.pending_count }}</span>
|
||||
@endif
|
||||
</a>
|
||||
<a href="/admin/approve" class="admin-bar-link" title="Pending approvals">
|
||||
<i class="fa-solid fa-clock"></i> Pending
|
||||
@if(typeof session.pending_count !== 'undefined' && session.pending_count > 0)
|
||||
<span class="admin-bar-badge">{{ session.pending_count }}</span>
|
||||
@endif
|
||||
</a>
|
||||
<a href="/mod" class="admin-bar-link" title="Moderator panel">
|
||||
<i class="fa-solid fa-gavel"></i> Mod
|
||||
</a>
|
||||
<a href="/admin/audit" class="admin-bar-link" title="Audit log">
|
||||
<i class="fa-solid fa-scroll"></i> Audit
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Preview As role toggle (right-aligned) -->
|
||||
<div class="admin-bar-preview" id="admin-bar-preview-wrap" style="margin-left: auto;">
|
||||
<span class="admin-bar-preview-label"><i class="fa-solid fa-eye" style="opacity:0.6;"></i> Preview:</span>
|
||||
<div class="admin-bar-preview-pills" id="admin-bar-preview-pills">
|
||||
<button class="admin-bar-preview-pill active" data-role="admin" title="View as yourself (admin)">Admin</button>
|
||||
<button class="admin-bar-preview-pill" data-role="mod" title="Simulate moderator view">Mod</button>
|
||||
<button class="admin-bar-preview-pill" data-role="user" title="Simulate regular user view">User</button>
|
||||
</div>
|
||||
<!-- Preview As role toggle (right-aligned) -->
|
||||
<div class="admin-bar-preview" id="admin-bar-preview-wrap" style="margin-left: auto;">
|
||||
<span class="admin-bar-preview-label"><i class="fa-solid fa-eye" style="opacity:0.6;"></i> Preview:</span>
|
||||
<div class="admin-bar-preview-pills" id="admin-bar-preview-pills">
|
||||
<button class="admin-bar-preview-pill active" data-role="admin" title="View as yourself (admin)">Admin</button>
|
||||
<button class="admin-bar-preview-pill" data-role="mod" title="Simulate moderator view">Mod</button>
|
||||
<button class="admin-bar-preview-pill" data-role="user" title="Simulate regular user view">User</button>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<button class="admin-bar-close-btn" id="admin-bar-close" title="Close admin bar (^ or ~)" aria-label="Close admin bar">
|
||||
<i class="fa-solid fa-xmark"></i>
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</nav>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Floating exit-preview pill (visible only during role preview) -->
|
||||
@if(session.admin)
|
||||
@@ -380,6 +373,7 @@
|
||||
@if(session && session.admin)
|
||||
<div class="shortcut-item"><span><kbd>x</kbd></span><span>{{ t('shortcuts.delete') }}</span></div>
|
||||
<div class="shortcut-item"><span><kbd>p</kbd></span><span>{{ t('shortcuts.toggle_rating') }}</span></div>
|
||||
<div class="shortcut-item"><span><kbd>^</kbd> / <kbd>~</kbd></span><span>Toggle Admin Bar</span></div>
|
||||
@endif
|
||||
<div class="shortcut-item"><span><kbd>Space</kbd></span><span>{{ t('shortcuts.play_pause') }}</span></div>
|
||||
<div class="shortcut-item"><span><kbd>←</kbd> <kbd>→</kbd></span><span>{{ t('shortcuts.next_prev') }}</span></div>
|
||||
@@ -483,6 +477,7 @@
|
||||
window.onRecaptchaModalReady = function() {
|
||||
_rcLoaded = true;
|
||||
renderModalRecaptcha();
|
||||
if (typeof window.renderReportRecaptcha === 'function') window.renderReportRecaptcha();
|
||||
};
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
@@ -506,23 +501,111 @@
|
||||
<style>
|
||||
/* ── Admin Bar ──────────────────────────────────────────────────────────────── */
|
||||
.admin-bar {
|
||||
width: 100%;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
left: var(--admin-bar-left, 0px);
|
||||
right: var(--admin-bar-right, 300px);
|
||||
width: auto;
|
||||
font-family: var(--font, system-ui, sans-serif);
|
||||
font-size: 0.76rem;
|
||||
z-index: 900;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.35);
|
||||
border-bottom: 1px solid rgba(255, 180, 0, 0.18);
|
||||
z-index: 100080 !important;
|
||||
box-shadow: 0 -3px 14px rgba(0, 0, 0, 0.5);
|
||||
border-top: 1px solid rgba(255, 190, 0, 0.28);
|
||||
border-bottom: none;
|
||||
transform: translateY(100%);
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1),
|
||||
opacity 0.22s ease,
|
||||
left 0.3s ease-in-out,
|
||||
right 0.3s ease-in-out;
|
||||
}
|
||||
|
||||
.admin-bar.open {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
body.onara-modal-open .admin-bar {
|
||||
z-index: 100080 !important;
|
||||
filter: none !important;
|
||||
}
|
||||
|
||||
body.onara-modal-open .admin-bar.open {
|
||||
opacity: 1 !important;
|
||||
visibility: visible !important;
|
||||
pointer-events: auto !important;
|
||||
}
|
||||
|
||||
/* Sidebar boundaries via CSS */
|
||||
body.sidebar-right-hidden .admin-bar {
|
||||
right: 0 !important;
|
||||
}
|
||||
|
||||
@media (max-width: 999px) {
|
||||
.admin-bar {
|
||||
right: min(280px, 80vw);
|
||||
}
|
||||
body.sidebar-right-hidden .admin-bar {
|
||||
right: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
body:not(:has(.global-sidebar-right, .item-sidebar-right, .index-sidebar-right)) .admin-bar {
|
||||
right: 0 !important;
|
||||
}
|
||||
|
||||
@media (min-width: 1000px) {
|
||||
body.layout-modern:not(.sidebar-left-hidden):not(.comments-hidden):has(.item-sidebar-left) .admin-bar {
|
||||
left: min(350px, 30vw);
|
||||
}
|
||||
body.layout-modern.sidebar-left-hidden .admin-bar,
|
||||
body.layout-modern.comments-hidden .admin-bar,
|
||||
body:has(.item-layout-container.sidebar-hidden) .admin-bar {
|
||||
left: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Lift bottom pagination smoothly if admin bar is open */
|
||||
body.admin-bar-open .pagination-container-fluid {
|
||||
bottom: 32px;
|
||||
}
|
||||
.pagination-container-fluid {
|
||||
transition: right 0.3s ease-in-out, bottom 0.3s ease-in-out !important;
|
||||
}
|
||||
|
||||
.admin-bar-close-btn {
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: rgba(255, 210, 80, 0.45);
|
||||
cursor: pointer;
|
||||
padding: 3px 7px;
|
||||
font-size: 0.8rem;
|
||||
border-radius: 3px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: 6px;
|
||||
transition: color 0.15s, background 0.15s;
|
||||
flex-shrink: 0;
|
||||
line-height: 1;
|
||||
}
|
||||
.admin-bar-close-btn:hover {
|
||||
color: #ffd844;
|
||||
background: rgba(255, 200, 0, 0.15);
|
||||
}
|
||||
|
||||
/* Normal admin bar */
|
||||
.admin-bar-inner {
|
||||
background: linear-gradient(90deg, #1a1200 0%, #1e1600 40%, #141000 100%);
|
||||
border-top: 1px solid rgba(255, 190, 0, 0.22);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 0 14px;
|
||||
height: 30px;
|
||||
height: 32px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@@ -647,7 +730,7 @@
|
||||
border: 1px solid rgba(255, 200, 0, 0.3);
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 6px 20px rgba(0,0,0,0.5);
|
||||
z-index: 9999;
|
||||
z-index: 100090 !important;
|
||||
display: none;
|
||||
overflow: hidden;
|
||||
min-width: 180px;
|
||||
@@ -846,6 +929,7 @@ body.preview-as-user .admin-pin-btn,
|
||||
body.preview-as-user .delete-btn,
|
||||
body.preview-as-user .poll-delete-btn,
|
||||
body.preview-as-user [data-is-admin] .admin-only { display: none !important; }
|
||||
body.preview-as-user .item_title_text { display: inline !important; }
|
||||
|
||||
/* Hide admin-only actions in mod preview (keep delete — mods can delete) */
|
||||
body.preview-as-mod #a_rethumb,
|
||||
@@ -866,6 +950,117 @@ body.preview-as-mod .btn-infobox-action { display: none !important; }
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
/* ── Admin bar: Sidebar Width Awareness & Hotkey Toggle ────────────────── */
|
||||
const adminBar = document.getElementById('admin-bar');
|
||||
const closeBtn = document.getElementById('admin-bar-close');
|
||||
const closeImpBtn = document.getElementById('admin-bar-close-imp');
|
||||
|
||||
function syncAdminBarSidebars() {
|
||||
if (!adminBar) return;
|
||||
|
||||
// Right sidebar detection
|
||||
const rightSb = document.querySelector('.global-sidebar-right, .item-sidebar-right, .index-sidebar-right');
|
||||
const isRightHidden = !rightSb ||
|
||||
document.body.classList.contains('sidebar-right-hidden') ||
|
||||
window.getComputedStyle(rightSb).display === 'none' ||
|
||||
window.getComputedStyle(rightSb).visibility === 'hidden';
|
||||
|
||||
let rw = 0;
|
||||
if (!isRightHidden && rightSb) {
|
||||
rw = rightSb.offsetWidth || (window.innerWidth <= 999 ? Math.min(280, Math.round(window.innerWidth * 0.8)) : 300);
|
||||
}
|
||||
|
||||
// Left sidebar detection (modern layout item page)
|
||||
const leftSb = document.querySelector('.item-sidebar-left');
|
||||
const isLeftHidden = !leftSb ||
|
||||
document.body.classList.contains('sidebar-left-hidden') ||
|
||||
document.body.classList.contains('comments-hidden') ||
|
||||
window.innerWidth < 1000 ||
|
||||
window.getComputedStyle(leftSb).display === 'none' ||
|
||||
window.getComputedStyle(leftSb).visibility === 'hidden' ||
|
||||
!!document.querySelector('.item-layout-container.sidebar-hidden');
|
||||
|
||||
let lw = 0;
|
||||
if (!isLeftHidden && leftSb) {
|
||||
lw = leftSb.offsetWidth || Math.min(350, Math.round(window.innerWidth * 0.3));
|
||||
}
|
||||
|
||||
adminBar.style.setProperty('--admin-bar-right', rw + 'px');
|
||||
adminBar.style.setProperty('--admin-bar-left', lw + 'px');
|
||||
adminBar.style.right = rw + 'px';
|
||||
adminBar.style.left = lw + 'px';
|
||||
}
|
||||
|
||||
function openAdminBar() {
|
||||
if (!adminBar) return;
|
||||
syncAdminBarSidebars();
|
||||
adminBar.classList.add('open');
|
||||
document.body.classList.add('admin-bar-open');
|
||||
}
|
||||
|
||||
function closeAdminBar() {
|
||||
if (!adminBar) return;
|
||||
adminBar.classList.remove('open');
|
||||
document.body.classList.remove('admin-bar-open');
|
||||
}
|
||||
|
||||
function toggleAdminBar() {
|
||||
if (!adminBar) return;
|
||||
if (adminBar.classList.contains('open')) {
|
||||
closeAdminBar();
|
||||
} else {
|
||||
openAdminBar();
|
||||
}
|
||||
}
|
||||
|
||||
if (closeBtn) closeBtn.addEventListener('click', closeAdminBar);
|
||||
if (closeImpBtn) closeImpBtn.addEventListener('click', closeAdminBar);
|
||||
|
||||
window.addEventListener('keydown', (e) => {
|
||||
// Never trigger if focus is inside an input, textarea, select or contenteditable
|
||||
const target = e.target;
|
||||
const tag = (target && target.tagName) ? target.tagName.toUpperCase() : '';
|
||||
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || (target && target.isContentEditable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Hotkey: '^' or '~' (also match Backquote key without modifier keys)
|
||||
const isHotkey = (
|
||||
e.key === '^' ||
|
||||
e.key === '~' ||
|
||||
(e.code === 'Backquote' && !e.ctrlKey && !e.metaKey && !e.altKey)
|
||||
);
|
||||
|
||||
if (isHotkey) {
|
||||
e.preventDefault();
|
||||
toggleAdminBar();
|
||||
return;
|
||||
}
|
||||
|
||||
// Escape closes admin bar if open
|
||||
if (e.key === 'Escape' && adminBar && adminBar.classList.contains('open')) {
|
||||
closeAdminBar();
|
||||
}
|
||||
});
|
||||
|
||||
// Watch body class changes for sidebar toggles
|
||||
if (window.MutationObserver) {
|
||||
const bodyObserver = new MutationObserver(() => {
|
||||
syncAdminBarSidebars();
|
||||
});
|
||||
bodyObserver.observe(document.body, { attributes: true, attributeFilter: ['class'] });
|
||||
}
|
||||
|
||||
window.addEventListener('resize', syncAdminBarSidebars, { passive: true });
|
||||
document.addEventListener('f0ck:contentLoaded', syncAdminBarSidebars);
|
||||
document.addEventListener('transitionend', (e) => {
|
||||
if (e.target && (e.target.classList.contains('global-sidebar-right') ||
|
||||
e.target.classList.contains('item-sidebar-right') ||
|
||||
e.target.classList.contains('item-sidebar-left'))) {
|
||||
syncAdminBarSidebars();
|
||||
}
|
||||
});
|
||||
|
||||
/* ── Admin bar: View-As (impersonation) ────────────────────────────────── */
|
||||
const input = document.getElementById('admin-viewas-input');
|
||||
const suggestBox = document.getElementById('admin-viewas-suggestions');
|
||||
|
||||
Reference in New Issue
Block a user