ghost rider
This commit is contained in:
@@ -10,8 +10,8 @@
|
||||
<div class="gapLeft"></div>
|
||||
</div>
|
||||
@if(enable_item_title)
|
||||
<div class="item_title">
|
||||
{!! item.title || '' !!}
|
||||
<div class="item_title item-title">
|
||||
<span class="item_title_text" @if(can_manage_item) style="display:none;" @endif>{!! item.title || '' !!}</span>
|
||||
@if(can_manage_item)
|
||||
<div class="info-title-edit-wrap">
|
||||
<input type="text" id="info-title-input" class="info-title-input" value="{!! item.title || '' !!}" placeholder="Add a title…" maxlength="500" data-item-id="{{ item.id }}" />
|
||||
@@ -93,9 +93,10 @@
|
||||
|
||||
<div class="blahlol">
|
||||
@if(user_alternative_infobox)
|
||||
<div class="user-infobox-block" data-banner-user="{{ item.username || '' }}" style="--author-accent: @if(item.author_color){{ item.author_color }}@else var(--accent) @endif; --author-border: @if(item.author_color){{ item.author_color }}@else var(--accent) @endif;@if(item.author_banner_file && user_banner_enabled) --author-banner: url('/a/{{ item.author_banner_file }}'); --author-banner-position: {{ item.author_banner_position === 'center' ? 'center top' : (item.author_banner_position || 'center top') }}; --author-banner-size: {{ item.author_banner_size || 'cover' }};@endif">
|
||||
<div class="user-infobox-block" data-banner-user="@if(session || !guest_anonymize){{ item.username || '' }}@endif" style="--author-accent: @if(session || !guest_anonymize)@if(item.author_color){{ item.author_color }}@else var(--accent) @endif@else var(--accent) @endif; --author-border: @if(session || !guest_anonymize)@if(item.author_color){{ item.author_color }}@else var(--accent) @endif@else var(--accent) @endif;@if(session || !guest_anonymize)@if(item.author_banner_file && user_banner_enabled) --author-banner: url('/a/{{ item.author_banner_file }}'); --author-banner-position: {{ item.author_banner_position === 'center' ? 'center top' : (item.author_banner_position || 'center top') }}; --author-banner-size: {{ item.author_banner_size || 'cover' }};@endif@endif">
|
||||
|
||||
<div class="user-infobox-avatar">
|
||||
@if(session || !guest_anonymize)
|
||||
<a href="/user/{{ (item.username || '').toLowerCase() }}">
|
||||
@if(item.author_avatar_file)
|
||||
<img src="/a/{{ item.author_avatar_file }}" />
|
||||
@@ -105,6 +106,11 @@
|
||||
<img src="/a/default.png" style="border-color: #444;" />
|
||||
@endif
|
||||
</a>
|
||||
@else
|
||||
<span>
|
||||
<img src="/a/default.png" style="border-color: #444;" />
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<div class="user-infobox-info">
|
||||
<div class="user-infobox-header">
|
||||
@@ -119,7 +125,7 @@
|
||||
</div>
|
||||
<div class="user-infobox-body">
|
||||
<div class="user-infobox-description">
|
||||
{!! item.author_description || '' !!}
|
||||
@if(session || !guest_anonymize){!! item.author_description || '' !!}@endif
|
||||
</div>
|
||||
<div class="user-infobox-actions">
|
||||
@if(session)
|
||||
@@ -177,10 +183,12 @@
|
||||
@endif
|
||||
@if(is_mod_or_admin)
|
||||
<i class="iconset fa-solid fa-thumbtack{{ item.is_pinned ? ' active' : '' }}" id="a_pin" data-pinned="{{ item.is_pinned }}" title="{{ item.is_pinned ? 'Unpin from main' : 'Pin to main' }}"></i>
|
||||
<i class="iconset fa-solid fa-ban{{ item.visibility === 3 ? ' active' : '' }}" id="a_unavailable" data-item-id="{{ item.id }}" data-visibility="{{ item.visibility || 0 }}" title="{{ item.visibility === 3 ? 'Make Available (Public)' : 'Make Unavailable (451)' }}" @if(item.visibility === 3) style="color: var(--danger, #ff4444);" @endif></i>
|
||||
<i class="iconset fa-solid fa-xmark" id="a_delete" title="Delete"></i>
|
||||
@endif
|
||||
@else
|
||||
<i class="iconset fa-solid fa-circle-info" id="a_info" data-item-id="{{ item.id }}" title="{{ t('info_modal.button_title') || 'Post & File Info' }}"></i>
|
||||
<i class="iconset fa-solid fa-triangle-exclamation report-item-btn" data-item-id="{{ item.id }}" title="Report this post"></i>
|
||||
@endif
|
||||
</div>
|
||||
<span class="badge badge-dark" id="tags" data-can-manage="{{ can_manage_item ? 'true' : 'false' }}">
|
||||
@@ -295,6 +303,15 @@
|
||||
<span style="font-size: 0.85em; color: var(--text-muted, #aaa);">Only visible to you and site moderators.</span>
|
||||
</div>
|
||||
</label>
|
||||
@if(is_mod_or_admin)
|
||||
<label class="visibility-option" style="display: flex; align-items: flex-start; gap: 12px; padding: 12px; border: 1px solid var(--nav-border-color, #444); border-radius: 6px; cursor: pointer; background: var(--bg-secondary, rgba(255,255,255,0.03));">
|
||||
<input type="radio" name="visibility" value="3" style="margin-top: 3px;" />
|
||||
<div>
|
||||
<strong style="display: block; color: var(--text-color, #fff);"><i class="fa-solid fa-ban" style="color: var(--danger, #ff4444);"></i> Unavailable (451)</strong>
|
||||
<span style="font-size: 0.85em; color: var(--text-muted, #aaa);">Only visible to administrators. Serves HTTP 451 on direct media for all other users.</span>
|
||||
</div>
|
||||
</label>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -74,8 +74,8 @@
|
||||
<div class="gapLeft"></div>
|
||||
</div>
|
||||
@if(enable_item_title)
|
||||
<div class="item_title">
|
||||
{!! item.title || '' !!}
|
||||
<div class="item_title item-title">
|
||||
<span class="item_title_text" @if(can_manage_item) style="display:none;" @endif>{!! item.title || '' !!}</span>
|
||||
@if(can_manage_item)
|
||||
<div class="info-title-edit-wrap">
|
||||
<input type="text" id="info-title-input" class="info-title-input" value="{!! item.title || '' !!}" placeholder="Add a title…" maxlength="500" data-item-id="{{ item.id }}" />
|
||||
@@ -173,10 +173,12 @@
|
||||
@endif
|
||||
@if(is_mod_or_admin)
|
||||
<i class="iconset fa-solid fa-thumbtack{{ item.is_pinned ? ' active' : '' }}" id="a_pin" data-pinned="{{ item.is_pinned }}" title="{{ item.is_pinned ? 'Unpin from main' : 'Pin to main' }}"></i>
|
||||
<i class="iconset fa-solid fa-ban{{ item.visibility === 3 ? ' active' : '' }}" id="a_unavailable" data-item-id="{{ item.id }}" data-visibility="{{ item.visibility || 0 }}" title="{{ item.visibility === 3 ? 'Make Available (Public)' : 'Make Unavailable (451)' }}" @if(item.visibility === 3) style="color: var(--danger, #ff4444);" @endif></i>
|
||||
<i class="iconset fa-solid fa-xmark" id="a_delete" title="Delete"></i>
|
||||
@endif
|
||||
@else
|
||||
<i class="iconset fa-solid fa-circle-info" id="a_info" data-item-id="{{ item.id }}" title="{{ t('info_modal.button_title') || 'Post & File Info' }}"></i>
|
||||
<i class="iconset fa-solid fa-triangle-exclamation report-item-btn" data-item-id="{{ item.id }}" title="Report this post"></i>
|
||||
@endif
|
||||
</div>
|
||||
<span id="favs" @if(!item.favorites.length) hidden@endif style="margin-top: 5px;">
|
||||
@@ -235,6 +237,15 @@
|
||||
<span style="font-size: 0.85em; color: var(--text-muted, #aaa);">Only visible to you and site moderators.</span>
|
||||
</div>
|
||||
</label>
|
||||
@if(is_mod_or_admin)
|
||||
<label class="visibility-option" style="display: flex; align-items: flex-start; gap: 12px; padding: 12px; border: 1px solid var(--nav-border-color, #444); border-radius: 6px; cursor: pointer; background: var(--bg-secondary, rgba(255,255,255,0.03));">
|
||||
<input type="radio" name="visibility" value="3" style="margin-top: 3px;" />
|
||||
<div>
|
||||
<strong style="display: block; color: var(--text-color, #fff);"><i class="fa-solid fa-ban" style="color: var(--danger, #ff4444);"></i> Unavailable (451)</strong>
|
||||
<span style="font-size: 0.85em; color: var(--text-muted, #aaa);">Only visible to administrators. Serves HTTP 451 on direct media for all other users.</span>
|
||||
</div>
|
||||
</label>
|
||||
@endif
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
+42
-3
@@ -136,10 +136,17 @@ window.loadReports = async function(page = 1) {
|
||||
'<button class="btn btn-sm btn-danger" onclick="window.resolveReport(' + r.id + ', "rejected")">Reject</button>';
|
||||
}
|
||||
|
||||
let reporterHtml = '';
|
||||
if (r.reporter_name) {
|
||||
reporterHtml = '<a href="/user/' + r.reporter_name + '" style="color: var(--accent); font-weight: bold;">' + r.reporter_name + '</a>' + (r.reporter_ip ? ' <span style="font-size: 0.75rem; color: #888;">(' + r.reporter_ip + ')</span>' : '');
|
||||
} else {
|
||||
reporterHtml = '<span style="color: #aaa; font-style: italic;">Guest' + (r.reporter_ip ? ' (' + r.reporter_ip + ')' : '') + '</span>';
|
||||
}
|
||||
|
||||
const tr = document.createElement('tr');
|
||||
tr.innerHTML =
|
||||
'<td data-label="ID">' + r.id + '</td>' +
|
||||
'<td data-label="Reporter"><a href="/user/' + r.reporter_name + '" style="color: var(--accent); font-weight: bold;">' + r.reporter_name + '</a></td>' +
|
||||
'<td data-label="Reporter">' + reporterHtml + '</td>' +
|
||||
'<td data-label="Target">' + targetHtml + '</td>' +
|
||||
'<td data-label="Reason"><span style="opacity: 0.8;">' + r.reason + '</span></td>' +
|
||||
'<td data-label="Date"><span style="font-size: 0.85rem; color: #888;">' + new Date(r.created_at).toLocaleString() + '</span></td>' +
|
||||
@@ -255,9 +262,11 @@ window.expandItem = function(e, id) {
|
||||
}
|
||||
|
||||
let buttonsHtml = '';
|
||||
// Delete Video button only for direct Video reports
|
||||
// Delete Item and Make Unavailable buttons for direct Item reports
|
||||
if (isItem && !isComment) {
|
||||
buttonsHtml += '<button class="btn btn-danger" onclick="window.adminDeleteItem(' + r.resolved_item_id + ')">Delete Item</button>';
|
||||
const isUnav = r.resolved_item_visibility === 3;
|
||||
buttonsHtml += ' <button class="btn ' + (isUnav ? 'btn-success' : 'btn-warning') + '" onclick="window.modToggleUnavailable(' + r.resolved_item_id + ', ' + (r.resolved_item_visibility || 0) + ')">' + (isUnav ? 'Make Available' : 'Make Unavailable (451)') + '</button>';
|
||||
}
|
||||
if (isComment) {
|
||||
buttonsHtml += '<button class="btn btn-danger" onclick="window.adminDeleteComment(' + r.comment_id + ')">Delete Comment</button>';
|
||||
@@ -289,7 +298,7 @@ window.expandItem = function(e, id) {
|
||||
'<div style="font-weight: bold; opacity: 1; text-transform: uppercase; font-size: 0.8rem; letter-spacing: 1.5px; margin-bottom: 5px;">Moderation Action:</div>' +
|
||||
'<div style="display: flex; gap: 12px; flex-wrap: wrap; justify-content: center;">' +
|
||||
buttonsHtml +
|
||||
'<button class="btn btn-secondary" onclick="window.modWarnUser(' + r.reporter_id + ')">Warn Reporter (' + r.reporter_name + ')</button>' +
|
||||
(r.reporter_id && r.reporter_name ? '<button class="btn btn-secondary" onclick="window.modWarnUser(' + r.reporter_id + ')">Warn Reporter (' + r.reporter_name + ')</button>' : (r.reporter_ip ? '<span style="color: #888; font-size: 0.85rem;">Reporter: Guest (' + r.reporter_ip + ')</span>' : '')) +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
'</div>' +
|
||||
@@ -335,6 +344,36 @@ window.adminDeleteItem = function(id) {
|
||||
});
|
||||
};
|
||||
|
||||
window.modToggleUnavailable = function(id, currentVis) {
|
||||
const willBeUnavailable = currentVis !== 3;
|
||||
const targetVis = willBeUnavailable ? 3 : 0;
|
||||
const actionText = willBeUnavailable ? 'Make Unavailable (serves HTTP 451 to non-logged in visitors)' : 'Make Available (Public)';
|
||||
|
||||
window.ModAction.confirm('Item Visibility', actionText + ' for item #' + id + '?', async () => {
|
||||
const params = new URLSearchParams();
|
||||
params.append('postid', id);
|
||||
params.append('id', id);
|
||||
params.append('visibility', targetVis);
|
||||
const res = await fetch('/api/v2/item/visibility', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-CSRF-Token': window.f0ckSession?.csrf_token
|
||||
},
|
||||
body: params
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
if (window.showFlash) window.showFlash(willBeUnavailable ? 'Item marked unavailable (451)' : 'Item restored to public', 'success');
|
||||
const item = window.currentReports.find(x => x.resolved_item_id === id);
|
||||
if (item) item.resolved_item_visibility = targetVis;
|
||||
window.loadReports(window.currentPage);
|
||||
} else {
|
||||
throw new Error(data.msg || 'Failed to update visibility');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
window.modWarnUser = function(userId) {
|
||||
window.ModAction.confirm('Warn User ID ' + userId, '', async (reason) => {
|
||||
const params = new URLSearchParams();
|
||||
|
||||
@@ -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