fdsafs
This commit is contained in:
+265
-17
@@ -87,6 +87,99 @@
|
||||
.btn-role { background: rgba(180, 100, 255, 0.15); color: #c084fc; border: 1px solid rgba(180, 100, 255, 0.3); }
|
||||
.btn-role:hover { background: rgba(180, 100, 255, 0.25); opacity: 1; }
|
||||
|
||||
.legacy-filter-btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
cursor: pointer;
|
||||
padding: 0 14px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
font-size: 0.85rem;
|
||||
color: #aaa;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
transition: all 0.2s ease;
|
||||
height: 42px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.legacy-filter-btn:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
color: #fff;
|
||||
}
|
||||
.legacy-filter-btn.active {
|
||||
background: rgba(var(--accent-rgb, 0, 150, 255), 0.15);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
}
|
||||
.legacy-filter-btn input {
|
||||
cursor: pointer;
|
||||
accent-color: var(--accent);
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.admin-select-filter {
|
||||
padding: 0 14px;
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
border-radius: 8px;
|
||||
color: #ccc;
|
||||
font-size: 0.85rem;
|
||||
outline: none;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
height: 42px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.admin-select-filter:hover {
|
||||
background: rgba(255, 255, 255, 0.08);
|
||||
border-color: rgba(255, 255, 255, 0.2);
|
||||
color: #fff;
|
||||
}
|
||||
.admin-select-filter:focus {
|
||||
border-color: var(--accent);
|
||||
}
|
||||
.admin-select-filter.active {
|
||||
background: rgba(var(--accent-rgb, 0, 150, 255), 0.15);
|
||||
border-color: var(--accent);
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
.admin-select-filter option {
|
||||
background: #1a1a1a;
|
||||
color: #fff;
|
||||
}
|
||||
.admin-select-filter:disabled {
|
||||
opacity: 0.35;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-filter-reset {
|
||||
background: rgba(255, 255, 255, 0.05);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
color: #aaa;
|
||||
padding: 0 14px;
|
||||
height: 42px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
font-size: 0.85rem;
|
||||
transition: all 0.2s;
|
||||
box-sizing: border-box;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.btn-filter-reset:hover {
|
||||
background: rgba(255, 60, 60, 0.15);
|
||||
border-color: rgba(255, 60, 60, 0.3);
|
||||
color: #ff6b6b;
|
||||
}
|
||||
|
||||
/* Create User Modal */
|
||||
#create-user-modal {
|
||||
display: none;
|
||||
@@ -236,28 +329,60 @@
|
||||
</div>
|
||||
|
||||
<div style="padding: 0 15px;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 30px; gap: 20px; flex-wrap: wrap;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-end; margin-bottom: 25px; gap: 20px; flex-wrap: wrap;">
|
||||
<div>
|
||||
<h2 style="margin: 0; font-weight: 800; letter-spacing: -0.5px;">User Management</h2>
|
||||
<p style="color: #888; margin: 5px 0 0 0;">Administration hub for <span id="total-count">{!! total !!}</span> registered members.</p>
|
||||
<p style="color: #888; margin: 5px 0 0 0;">Administration hub for <span id="total-count">{!! total !!}</span> <span id="total-label">{{ totalLabel }}</span>.</p>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; align-items: center; flex-grow: 1; max-width: 500px;">
|
||||
<div style="flex-grow: 1; position: relative;">
|
||||
<input type="text" id="user-search" placeholder='Search by name or email… use "exact" for exact match'
|
||||
style="width: 100%; padding: 12px 20px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; color: #fff; outline: none; transition: border-color 0.2s;"
|
||||
value="{{ q }}">
|
||||
<div id="search-spinner" style="position: absolute; right: 15px; top: 12px; display: none;">
|
||||
<svg class="spinner" width="20" height="20" viewBox="0 0 50 50" style="animation: rotate 2s linear infinite;">
|
||||
<circle cx="25" cy="25" r="20" fill="none" stroke="var(--accent)" stroke-width="5" stroke-dasharray="90,150" stroke-dashoffset="0" style="stroke-linecap: round;"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<button class="btn-modern btn-create" onclick="openCreateUserModal()" style="padding: 10px 16px; font-size: 0.85rem; white-space: nowrap;" title="Create new user">
|
||||
<div>
|
||||
<button class="btn-modern btn-create" onclick="openCreateUserModal()" style="padding: 11px 18px; font-size: 0.85rem; white-space: nowrap; height: 42px;" title="Create new user">
|
||||
<i class="fa fa-user-plus"></i> Create User
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filter & Search Toolbar -->
|
||||
<div style="display: flex; gap: 10px; align-items: center; margin-bottom: 25px; flex-wrap: wrap;">
|
||||
<div style="flex-grow: 1; min-width: 240px; position: relative;">
|
||||
<input type="text" id="user-search" placeholder='Search by name or email… use "exact" for exact match'
|
||||
style="width: 100%; height: 42px; padding: 0 20px; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1); border-radius: 8px; color: #fff; outline: none; transition: border-color 0.2s; box-sizing: border-box;"
|
||||
value="{{ q }}">
|
||||
<div id="search-spinner" style="position: absolute; right: 15px; top: 11px; display: none;">
|
||||
<svg class="spinner" width="20" height="20" viewBox="0 0 50 50" style="animation: rotate 2s linear infinite;">
|
||||
<circle cx="25" cy="25" r="20" fill="none" stroke="var(--accent)" stroke-width="5" stroke-dasharray="90,150" stroke-dashoffset="0" style="stroke-linecap: round;"></circle>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Status Filter -->
|
||||
<select id="status-filter" class="admin-select-filter {{ status ? 'active' : '' }}" {{ onlyLegacy ? 'disabled' : '' }} title="Filter by account status">
|
||||
<option value="">All Statuses</option>
|
||||
<option value="banned" {{ status === 'banned' ? 'selected' : '' }}>⛔ Banned</option>
|
||||
<option value="active" {{ status === 'active' ? 'selected' : '' }}>🟢 Active</option>
|
||||
<option value="pending" {{ status === 'pending' ? 'selected' : '' }}>⏳ Pending</option>
|
||||
</select>
|
||||
|
||||
<!-- Role Filter -->
|
||||
<select id="role-filter" class="admin-select-filter {{ role ? 'active' : '' }}" {{ onlyLegacy ? 'disabled' : '' }} title="Filter by user role">
|
||||
<option value="">All Roles</option>
|
||||
<option value="staff" {{ role === 'staff' ? 'selected' : '' }}>🛡️ Staff (All)</option>
|
||||
<option value="admin" {{ role === 'admin' ? 'selected' : '' }}>⚡ Admins</option>
|
||||
<option value="mod" {{ role === 'mod' ? 'selected' : '' }}>🗡️ Moderators</option>
|
||||
<option value="user" {{ role === 'user' ? 'selected' : '' }}>👤 Regular Users</option>
|
||||
</select>
|
||||
|
||||
<!-- Legacy Only Toggle -->
|
||||
<label id="legacy-filter-label" class="legacy-filter-btn {{ onlyLegacy ? 'active' : '' }}" title="Toggle to only show legacy ghost users">
|
||||
<input type="checkbox" id="legacy-filter" {{ onlyLegacy ? 'checked' : '' }}>
|
||||
<span><i class="fa fa-ghost" style="opacity: 0.8; margin-right: 4px;"></i>Only legacy</span>
|
||||
</label>
|
||||
|
||||
<!-- Reset Filters Button -->
|
||||
<button id="reset-filters-btn" class="btn-filter-reset" style="display: {{ (q || status || role || onlyLegacy) ? 'inline-flex' : 'none' }};" title="Reset all filters and search">
|
||||
<i class="fa fa-rotate-left"></i> Reset
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="upload-form">
|
||||
<table class="admin-users-table responsive-table">
|
||||
<thead>
|
||||
@@ -276,7 +401,7 @@
|
||||
</table>
|
||||
<div id="loading-trigger" style="height: 20px; margin-top: 10px;"></div>
|
||||
<div id="no-users-msg" style="display: {{ users.length === 0 ? 'block' : 'none' }}; padding: 40px; text-align: center; color: #666;">
|
||||
No users matched your search.
|
||||
{{ emptyMsg }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -502,28 +627,81 @@
|
||||
var hasMore = {!! hasMore ? 'true' : 'false' !!};
|
||||
var isLoading = false;
|
||||
var searchQuery = '{{ q }}';
|
||||
var onlyLegacy = {!! onlyLegacy ? 'true' : 'false' !!};
|
||||
var currentStatus = '{{ status }}';
|
||||
var currentRole = '{{ role }}';
|
||||
|
||||
var searchInput = document.getElementById('user-search');
|
||||
var statusFilter = document.getElementById('status-filter');
|
||||
var roleFilter = document.getElementById('role-filter');
|
||||
var legacyFilter = document.getElementById('legacy-filter');
|
||||
var legacyFilterLabel = document.getElementById('legacy-filter-label');
|
||||
var resetFiltersBtn = document.getElementById('reset-filters-btn');
|
||||
var totalLabel = document.getElementById('total-label');
|
||||
var tableBody = document.getElementById('user-table-body');
|
||||
var loadingTrigger = document.getElementById('loading-trigger');
|
||||
var noUsersMsg = document.getElementById('no-users-msg');
|
||||
var spinner = document.getElementById('search-spinner');
|
||||
var countSpan = document.getElementById('total-count');
|
||||
|
||||
function updateUrlAndControls() {
|
||||
var urlParams = new URLSearchParams();
|
||||
if (searchQuery) urlParams.set('q', searchQuery);
|
||||
if (onlyLegacy) {
|
||||
urlParams.set('legacy', '1');
|
||||
} else {
|
||||
if (currentStatus) urlParams.set('status', currentStatus);
|
||||
if (currentRole) urlParams.set('role', currentRole);
|
||||
}
|
||||
var newUrl = window.location.pathname + (urlParams.toString() ? ('?' + urlParams.toString()) : '');
|
||||
window.history.replaceState({}, '', newUrl);
|
||||
|
||||
// Update reset button
|
||||
var hasFilter = !!(searchQuery || onlyLegacy || currentStatus || currentRole);
|
||||
if (resetFiltersBtn) resetFiltersBtn.style.display = hasFilter ? 'inline-flex' : 'none';
|
||||
|
||||
// Update control states
|
||||
if (statusFilter) {
|
||||
statusFilter.disabled = onlyLegacy;
|
||||
statusFilter.classList.toggle('active', !!currentStatus && !onlyLegacy);
|
||||
}
|
||||
if (roleFilter) {
|
||||
roleFilter.disabled = onlyLegacy;
|
||||
roleFilter.classList.toggle('active', !!currentRole && !onlyLegacy);
|
||||
}
|
||||
if (legacyFilterLabel) {
|
||||
legacyFilterLabel.classList.toggle('active', onlyLegacy);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchUsers(page, q, append) {
|
||||
if (isLoading) return;
|
||||
isLoading = true;
|
||||
if (!append) spinner.style.display = 'block';
|
||||
|
||||
try {
|
||||
var url = '/admin/users?page=' + page + '&q=' + encodeURIComponent(q);
|
||||
var params = new URLSearchParams();
|
||||
params.set('page', page);
|
||||
if (q) params.set('q', q);
|
||||
if (onlyLegacy) {
|
||||
params.set('legacy', '1');
|
||||
} else {
|
||||
if (currentStatus) params.set('status', currentStatus);
|
||||
if (currentRole) params.set('role', currentRole);
|
||||
}
|
||||
|
||||
var url = '/admin/users?' + params.toString();
|
||||
var res = await fetch(url, { headers: { 'X-Requested-With': 'XMLHttpRequest' } });
|
||||
|
||||
// Update state from headers
|
||||
var total = res.headers.get('X-Total-Count');
|
||||
var hasMoreHeader = res.headers.get('X-Has-More');
|
||||
var totalLabelHeader = res.headers.get('X-Total-Label');
|
||||
var emptyMsgHeader = res.headers.get('X-Empty-Msg');
|
||||
|
||||
if (total !== null) countSpan.textContent = total;
|
||||
if (totalLabelHeader && totalLabel) totalLabel.textContent = totalLabelHeader;
|
||||
if (emptyMsgHeader && noUsersMsg) noUsersMsg.textContent = emptyMsgHeader;
|
||||
if (hasMoreHeader !== null) hasMore = (hasMoreHeader === 'true');
|
||||
|
||||
var html = await res.text();
|
||||
@@ -548,12 +726,74 @@
|
||||
searchInput.addEventListener('input', function() {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(function() {
|
||||
searchQuery = searchInput.value;
|
||||
searchQuery = searchInput.value.trim();
|
||||
currentPage = 1;
|
||||
updateUrlAndControls();
|
||||
fetchUsers(1, searchQuery, false);
|
||||
}, 300);
|
||||
});
|
||||
|
||||
// Status Filter Handling
|
||||
if (statusFilter) {
|
||||
statusFilter.addEventListener('change', function() {
|
||||
currentStatus = this.value;
|
||||
if (currentStatus && onlyLegacy) {
|
||||
onlyLegacy = false;
|
||||
if (legacyFilter) legacyFilter.checked = false;
|
||||
}
|
||||
updateUrlAndControls();
|
||||
currentPage = 1;
|
||||
fetchUsers(1, searchQuery, false);
|
||||
});
|
||||
}
|
||||
|
||||
// Role Filter Handling
|
||||
if (roleFilter) {
|
||||
roleFilter.addEventListener('change', function() {
|
||||
currentRole = this.value;
|
||||
if (currentRole && onlyLegacy) {
|
||||
onlyLegacy = false;
|
||||
if (legacyFilter) legacyFilter.checked = false;
|
||||
}
|
||||
updateUrlAndControls();
|
||||
currentPage = 1;
|
||||
fetchUsers(1, searchQuery, false);
|
||||
});
|
||||
}
|
||||
|
||||
// Legacy Only Filter Handling
|
||||
if (legacyFilter) {
|
||||
legacyFilter.addEventListener('change', function() {
|
||||
onlyLegacy = this.checked;
|
||||
if (onlyLegacy) {
|
||||
currentStatus = '';
|
||||
currentRole = '';
|
||||
if (statusFilter) statusFilter.value = '';
|
||||
if (roleFilter) roleFilter.value = '';
|
||||
}
|
||||
updateUrlAndControls();
|
||||
currentPage = 1;
|
||||
fetchUsers(1, searchQuery, false);
|
||||
});
|
||||
}
|
||||
|
||||
// Reset All Filters
|
||||
if (resetFiltersBtn) {
|
||||
resetFiltersBtn.addEventListener('click', function() {
|
||||
searchQuery = '';
|
||||
searchInput.value = '';
|
||||
onlyLegacy = false;
|
||||
currentStatus = '';
|
||||
currentRole = '';
|
||||
if (legacyFilter) legacyFilter.checked = false;
|
||||
if (statusFilter) statusFilter.value = '';
|
||||
if (roleFilter) roleFilter.value = '';
|
||||
updateUrlAndControls();
|
||||
currentPage = 1;
|
||||
fetchUsers(1, '', false);
|
||||
});
|
||||
}
|
||||
|
||||
// Infinite Scroll
|
||||
var observer = new IntersectionObserver(function(entries) {
|
||||
if (entries[0].isIntersecting && hasMore && !isLoading) {
|
||||
@@ -624,6 +864,14 @@
|
||||
if (data.success) {
|
||||
closeCreateUserModal();
|
||||
showFlash(data.msg, 'success');
|
||||
// If we were filtering by legacy or banned, reset filter so admin can see newly created user
|
||||
if (onlyLegacy || currentStatus === 'banned') {
|
||||
onlyLegacy = false;
|
||||
currentStatus = '';
|
||||
if (legacyFilter) legacyFilter.checked = false;
|
||||
if (statusFilter) statusFilter.value = '';
|
||||
updateUrlAndControls();
|
||||
}
|
||||
// Refresh the table from page 1
|
||||
currentPage = 1;
|
||||
fetchUsers(1, searchQuery, false);
|
||||
|
||||
+66
-9
@@ -1,13 +1,70 @@
|
||||
@include(snippets/header)
|
||||
<div id="main" style="display: flex; justify-content: center; align-items: center; min-height: 80vh;">
|
||||
<div class="banned-container" style="background: rgba(0,0,0,0.85); padding: 40px; margin: 25px; border: 2px solid var(--accent); text-align: center; position: relative; z-index: 10002;">
|
||||
<h1 style="color: var(--accent); margin-bottom: 20px;">YOU ARE BANNED!</h1>
|
||||
<video style="max-width: 100%;" src="{{ ban_video }}" autoplay loop controls></video>
|
||||
<p style="font-size: 1.2em; margin-bottom: 20px;">Reason: <strong>{{ reason }}</strong></p>
|
||||
<p style="font-size: 1.1em; color: rgba(255,255,255,0.7);">Ban expires: <strong>{{ expires }}</strong></p>
|
||||
<div style="margin-top: 30px;">
|
||||
<a href="/logout" class="btn btn-outline-danger" style="padding: 10px 20px; border: 1px solid var(--accent); color: var(--accent); text-decoration: none; border-radius: 4px;">Leave</a>
|
||||
</div>
|
||||
<div class="pagewrapper">
|
||||
<div id="main" style="padding: 40px 15px; box-sizing: border-box;">
|
||||
<div style="min-height: 70vh; display: flex; align-items: center; justify-content: center;">
|
||||
|
||||
<!-- BANNED VIEW -->
|
||||
<div id="banned-view" class="banned-container" style="display: {{ isBanned ? 'block' : 'none' }}; background: rgba(18, 18, 20, 0.95); padding: 40px 30px; margin: 0 auto; text-align: center; max-width: 520px; width: 100%; box-sizing: border-box;">
|
||||
<h1 style="color: #ff4757; margin-bottom: 20px; font-weight: 800; letter-spacing: 1px; text-transform: uppercase;">YOU ARE BANNED!</h1>
|
||||
<img src="/s/img/banned_angry.png" alt="Banned" style="width: 170px; height: auto; margin: 0 auto 25px auto; display: block;" />
|
||||
<div style="background: rgba(0,0,0,0.45); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; padding: 14px 18px; margin-bottom: 25px; text-align: left;">
|
||||
<p style="margin: 0 0 8px 0; font-size: 0.95em; color: #ddd;">
|
||||
<span style="color: #888; font-size: 0.82em; text-transform: uppercase; display: block; margin-bottom: 2px;">Reason</span>
|
||||
<strong id="ban-reason-display" style="color: #ff6b6b; word-break: break-word;">{{ reason }}</strong>
|
||||
</p>
|
||||
<p style="margin: 0; font-size: 0.95em; color: #ddd;">
|
||||
<span style="color: #888; font-size: 0.82em; text-transform: uppercase; display: block; margin-bottom: 2px;">Ban expires</span>
|
||||
<strong id="ban-expires-display" style="color: #bbb;">{{ expires }}</strong>
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/logout" class="btn btn-outline-danger" style="padding: 10px 24px; border: 1px solid #e74c3c; color: #e74c3c; text-decoration: none; border-radius: 6px; font-weight: 600;">Leave</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- UNBANNED / GOOD STANDING VIEW -->
|
||||
<div id="unbanned-view" class="unbanned-container" style="display: {{ isBanned ? 'none' : 'block' }}; background: rgba(18, 18, 20, 0.95); padding: 40px 30px; margin: 0 auto; border: 2px solid #2ecc71; text-align: center; border-radius: 12px; max-width: 520px; width: 100%; box-shadow: 0 10px 50px rgba(46, 204, 113, 0.25); box-sizing: border-box;">
|
||||
<div style="width: 80px; height: 80px; border-radius: 50%; background: rgba(46, 204, 113, 0.15); display: flex; align-items: center; justify-content: center; margin: 0 auto 20px auto; border: 2px solid rgba(46, 204, 113, 0.4);">
|
||||
<i class="fa-solid fa-shield-halved" style="font-size: 38px; color: #2ecc71;"></i>
|
||||
</div>
|
||||
<h1 style="color: #2ecc71; margin-bottom: 10px; font-weight: 800; letter-spacing: 0.5px; text-transform: uppercase; font-size: 1.7em;">YOU ARE NOT BANNED</h1>
|
||||
<p style="font-size: 1em; color: #aaa; margin-bottom: 22px; line-height: 1.5;">
|
||||
Your account, connection, and device are in good standing. There are no active bans on this browser.
|
||||
</p>
|
||||
<div style="background: rgba(0,0,0,0.4); border: 1px solid rgba(255,255,255,0.08); border-radius: 8px; padding: 14px 18px; margin-bottom: 25px; text-align: left;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
|
||||
<span style="color: #888; font-size: 0.88em; text-transform: uppercase;">Status</span>
|
||||
<span style="color: #2ecc71; font-weight: 700; font-size: 0.9em;"><i class="fa-solid fa-circle" style="font-size: 0.6em; vertical-align: middle; margin-right: 4px;"></i> Clear / Active</span>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||
<span style="color: #888; font-size: 0.88em; text-transform: uppercase;">IP Address</span>
|
||||
<code style="color: #ddd; font-family: monospace; font-size: 0.9em;">{{ clientIp || '127.0.0.1' }}</code>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<a href="/" class="btn" style="background: #2ecc71; border: 1px solid #27ae60; color: #fff; padding: 10px 28px; border-radius: 6px; font-size: 1em; font-weight: 600; text-decoration: none; display: inline-block;">Return to Home</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(async function() {
|
||||
try {
|
||||
const raw = localStorage.getItem('f0ck_anon_tombstone');
|
||||
const tombstone = raw ? JSON.parse(raw) : null;
|
||||
if (tombstone && tombstone.banned) {
|
||||
const bView = document.getElementById('banned-view');
|
||||
const uView = document.getElementById('unbanned-view');
|
||||
if (bView) bView.style.display = 'block';
|
||||
if (uView) uView.style.display = 'none';
|
||||
const rEl = document.getElementById('ban-reason-display');
|
||||
const eEl = document.getElementById('ban-expires-display');
|
||||
if (rEl && tombstone.reason) rEl.textContent = tombstone.reason;
|
||||
if (eEl && tombstone.expires) eEl.textContent = new Date(tombstone.expires).toLocaleString();
|
||||
}
|
||||
} catch (e) {}
|
||||
})();
|
||||
</script>
|
||||
@include(snippets/footer)
|
||||
@@ -3,7 +3,7 @@
|
||||
@include(snippets/page-title)
|
||||
<div class="posts" data-current-page="{{ pagination.current }}" data-has-more="{{ pagination.next ? 'true' : 'false' }}">
|
||||
@each(items as item)
|
||||
<a href="{{ link.main }}{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}" class="{{ item.is_pinned ? 'anim-boxshadow ' : '' }}thumb lazy-thumb {{ item.has_notification ? 'has-notif' : '' }} {{ item.is_pinned ? 'is-pinned' : '' }} {{ item.is_onara_active ? 'onara-active' : '' }}" data-file="{{ item.dest }}" data-mime="{{ item.mime }}" data-user="@if(session || !guest_anonymize){!! item.display_name || item.username !!}@else anonymous@endif" data-ext="{{ item.mime.split('/')[1].replace('youtube', 'yt').replace('x-shockwave-flash', 'flash').replace('vnd.adobe.flash.movie', 'flash').toUpperCase() }}" data-mode="{{ item.tag_id == nsfl_tag_id ? 'nsfl' : (item.tag_id == 2 ? 'nsfw' : (item.tag_id == 1 ? 'sfw' : 'null')) }}" data-bg="/t/{{ item.id }}.webp" data-size="{{ enable_dynamic_thumbs ? (item.thumb_size || 1) : 1 }}">
|
||||
<a href="{{ link.main }}{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}" class="{{ item.is_pinned ? 'anim-boxshadow ' : '' }}thumb lazy-thumb {{ item.has_notification ? 'has-notif' : '' }} {{ item.is_pinned ? 'is-pinned' : '' }} {{ item.is_onara_active ? 'onara-active' : '' }}" data-file="{{ item.dest }}" data-mime="{{ item.mime }}" data-user="@if(!is_anonymized){!! item.display_name || item.username !!}@else anonymous@endif" data-ext="{{ item.mime.split('/')[1].replace('youtube', 'yt').replace('x-shockwave-flash', 'flash').replace('vnd.adobe.flash.movie', 'flash').toUpperCase() }}" data-mode="{{ item.tag_id == nsfl_tag_id ? 'nsfl' : (item.tag_id == 2 ? 'nsfw' : (item.tag_id == 1 ? 'sfw' : 'null')) }}" data-bg="/t/{{ item.id }}.webp" data-size="{{ enable_dynamic_thumbs ? (item.thumb_size || 1) : 1 }}">
|
||||
<div class="thumb-indicators">
|
||||
@if(item.is_pinned)
|
||||
<i class="fa-solid fa-thumbtack pin-indicator anim"></i>
|
||||
|
||||
@@ -93,10 +93,10 @@
|
||||
|
||||
<div class="blahlol">
|
||||
@if(user_alternative_infobox)
|
||||
<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-block" data-banner-user="@if(!is_anonymized){{ item.username || '' }}@endif" style="--author-accent: @if(!is_anonymized)@if(item.author_color){{ item.author_color }}@else var(--accent) @endif@else var(--accent) @endif; --author-border: @if(!is_anonymized)@if(item.author_color){{ item.author_color }}@else var(--accent) @endif@else var(--accent) @endif;@if(!is_anonymized)@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)
|
||||
@if(!is_anonymized)
|
||||
<a href="/user/{{ (item.username || '').toLowerCase() }}">
|
||||
@if(item.author_avatar_file)
|
||||
<img src="/a/{{ item.author_avatar_file }}" />
|
||||
@@ -115,8 +115,8 @@
|
||||
<div class="user-infobox-info">
|
||||
<div class="user-infobox-header">
|
||||
<div class="user-infobox-username-container">
|
||||
@if(session || !guest_anonymize)
|
||||
<a id="a_username" data-username="{{ item.username || '' }}" data-author-id="{{ item.author_id || '' }}" href="/user/{{ (item.username || '').toLowerCase() }}" tooltip="ID: {{ item.author_id }}" class="user-infobox-username">{!! item.author_display_name || item.username !!}</a>
|
||||
@if(!is_anonymized)
|
||||
<a id="a_username" data-username="{{ item.username || '' }}" @if(session && !session.is_anon) data-author-id="{{ item.author_id || '' }}" @endif href="/user/{{ (item.username || '').toLowerCase() }}" @if(session && !session.is_anon) tooltip="ID: {{ item.author_id }}" @endif class="user-infobox-username">{!! item.author_display_name || item.username !!}</a>
|
||||
@else
|
||||
<span class="user-infobox-username">anonymous</span>
|
||||
@endif
|
||||
@@ -125,7 +125,7 @@
|
||||
</div>
|
||||
<div class="user-infobox-body">
|
||||
<div class="user-infobox-description">
|
||||
@if(session || !guest_anonymize){!! item.author_description || '' !!}@endif
|
||||
@if(!is_anonymized){!! item.author_description || '' !!}@endif
|
||||
</div>
|
||||
<div class="user-infobox-actions">
|
||||
@if(session)
|
||||
@@ -145,7 +145,7 @@
|
||||
<span class="badge badge-dark">
|
||||
|
||||
<a href="/{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}" class="id-link" data-item-id="{{ item.id }}" @if(user_alternative_infobox)style="display:none"@endif>{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}</a>
|
||||
@if(!user_alternative_infobox) — [@if(session || !guest_anonymize)<a id="a_username" data-username="{{ item.username || '' }}" @if(session) data-author-id="{{ item.author_id || '' }}" @endif href="/user/{{ (item.username || '').toLowerCase() }}" @if(session && item.author_id) tooltip="ID: {{ item.author_id }}" @endif @if(item.author_color) style="color: {{ item.author_color }}" @endif>{!! item.author_display_name || item.username || 'unknown' !!}</a>@else<span>anonymous</span>@endif] @endif
|
||||
@if(!user_alternative_infobox) — [@if(!is_anonymized)<a id="a_username" data-username="{{ item.username || '' }}" @if(session && !session.is_anon) data-author-id="{{ item.author_id || '' }}" @endif href="/user/{{ (item.username || '').toLowerCase() }}" @if(session && !session.is_anon && item.author_id) tooltip="ID: {{ item.author_id }}" @endif @if(item.author_color) style="color: {{ item.author_color }}" @endif>{!! item.author_display_name || item.username || 'unknown' !!}</a>@else<span>anonymous</span>@endif] @endif
|
||||
@if(!user_alternative_infobox)
|
||||
<span id="oc-badge-container">@if(item.is_oc) — <span class="oc-badge" tooltip="Original Content">OC</span>@endif</span>
|
||||
@endif
|
||||
@@ -205,8 +205,8 @@
|
||||
<span class="rating-label">{!! tag.tag !!}</span>
|
||||
</span>
|
||||
@else
|
||||
<span @if(session && (tag.display_name || tag.user)) tooltip="{!! tag.display_name || tag.user !!}" @endif class="badge {{ tag.badge }}">
|
||||
<a href="/tag/{{ tag.normalized }}">{!! tag.tag !!}</a>@if(is_mod_or_admin) <a class="removetag" href="#"><i class="fa-solid fa-xmark"></i></a>@endif
|
||||
<span @if(!is_anonymized && session && !session.is_anon && (tag.display_name || tag.user)) tooltip="{!! tag.display_name || tag.user !!}" @endif class="badge {{ tag.badge }} tag-badge @if(tag.is_excluded) tag-is-excluded @endif" data-tag-id="{{ tag.id }}" data-tag="{{ tag.tag }}" data-tag-normalized="{{ tag.normalized }}">
|
||||
<a href="/tag/{{ tag.normalized }}" class="tag-name">{!! tag.tag !!}</a>@if(tag.can_exclude)<button type="button" class="tag-exclude-btn" title="@if(tag.is_excluded){{ t('filter.unexclude_tag') || 'Excluded (click to unexclude)' }}@else{{ t('filter.exclude_tag') || 'Exclude tag' }}@endif" aria-label="Exclude tag"><i class="fa-solid @if(tag.is_excluded) fa-circle-check @else fa-ban @endif"></i></button>@endif@if(is_mod_or_admin) <a class="removetag" href="#"><i class="fa-solid fa-xmark"></i></a>@endif
|
||||
</span>
|
||||
@endif
|
||||
@endeach
|
||||
@@ -227,7 +227,7 @@
|
||||
@each(item.favorites as fav)
|
||||
@if(fav.hide_fav_badge && (!session || session.user !== fav.user))
|
||||
<a class="ghost-fav" tooltip="?" flow="up" style="cursor: default;"><img src="/s/img/ghost_fav.svg" style="height: 32px; width: 32px;" loading="lazy" /></a>
|
||||
@elseif(!session && guest_anonymize)
|
||||
@elseif(is_anonymized)
|
||||
<a class="ghost-fav" tooltip="anonymous" flow="up" style="cursor: default;"><img src="/a/default.png" style="height: 32px; width: 32px;" loading="lazy" /></a>
|
||||
@else
|
||||
<a href="/user/{{ fav.user.toLowerCase() }}" tooltip="{!! fav.display_name || fav.user !!}" flow="up"><img src="@if(fav.avatar_file)/a/{{ fav.avatar_file }}@elseif(fav.avatar)/t/{{ fav.avatar }}.webp@else/a/default.png@endif" style="height: 32px; width: 32px@if(fav.username_color); border-color: {{ fav.username_color }}@endif" loading="lazy" /></a>
|
||||
@@ -252,6 +252,8 @@
|
||||
@if(item.is_comments_locked) data-is-locked="true" @endif>
|
||||
@if(item.is_comments_locked && !is_mod_or_admin)
|
||||
<div class="lock-notice">🔒 Comments are disabled on this thread.</div>
|
||||
@elseif(session && session.is_anon && !anon_permissions.comment)
|
||||
<div class="lock-notice">🔒 Anonymous comments are disabled.</div>
|
||||
@else
|
||||
<div class="comment-input main-input">
|
||||
<textarea disabled></textarea>
|
||||
|
||||
@@ -21,6 +21,8 @@
|
||||
@if(item.is_comments_locked) data-is-locked="true" @endif>
|
||||
@if(item.is_comments_locked && !is_mod_or_admin)
|
||||
<div class="lock-notice">🔒 Comments are disabled on this thread.</div>
|
||||
@elseif(session && session.is_anon && !anon_permissions.comment)
|
||||
<div class="lock-notice">🔒 Anonymous comments are disabled.</div>
|
||||
@else
|
||||
<div class="comment-input main-input">
|
||||
<textarea disabled></textarea>
|
||||
@@ -51,8 +53,8 @@
|
||||
<span class="rating-label">{!! tag.tag !!}</span>
|
||||
</span>
|
||||
@else
|
||||
<span @if(session && (tag.display_name || tag.user)) tooltip="{!! tag.display_name || tag.user !!}" @endif class="badge {{ tag.badge }}">
|
||||
<a href="/tag/{{ tag.normalized }}">{!! tag.tag !!}</a>@if(is_mod_or_admin) <a class="removetag" href="#"><i class="fa-solid fa-xmark"></i></a>@endif
|
||||
<span @if(!is_anonymized && session && !session.is_anon && (tag.display_name || tag.user)) tooltip="{!! tag.display_name || tag.user !!}" @endif class="badge {{ tag.badge }} tag-badge @if(tag.is_excluded) tag-is-excluded @endif" data-tag-id="{{ tag.id }}" data-tag="{{ tag.tag }}" data-tag-normalized="{{ tag.normalized }}">
|
||||
<a href="/tag/{{ tag.normalized }}" class="tag-name">{!! tag.tag !!}</a>@if(tag.can_exclude)<button type="button" class="tag-exclude-btn" title="@if(tag.is_excluded){{ t('filter.unexclude_tag') || 'Excluded (click to unexclude)' }}@else{{ t('filter.exclude_tag') || 'Exclude tag' }}@endif" aria-label="Exclude tag"><i class="fa-solid @if(tag.is_excluded) fa-circle-check @else fa-ban @endif"></i></button>@endif@if(is_mod_or_admin) <a class="removetag" href="#"><i class="fa-solid fa-xmark"></i></a>@endif
|
||||
</span>
|
||||
@endif
|
||||
@endeach
|
||||
@@ -138,7 +140,7 @@
|
||||
</div>
|
||||
<div class="blahlol">
|
||||
<span class="badge badge-dark">
|
||||
<a href="/{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}" class="id-link" data-item-id="{{ item.id }}">{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}</a> — [@if(session || !guest_anonymize)<a id="a_username" data-username="{{ item.username || '' }}" @if(session) data-author-id="{{ item.author_id || '' }}" @endif href="/user/{{ item_username_lower }}" @if(session && item.author_id) tooltip="ID: {{ item.author_id }}" @endif @if(item.author_color) style="color: {{ item.author_color }}" @endif>{!! item.author_display_name || item.username || 'unknown' !!}</a>@else<span>anonymous</span>@endif] <span id="oc-badge-container">@if(item.is_oc) — <span class="oc-badge" tooltip="Original Content">OC</span>@endif</span>
|
||||
<a href="/{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}" class="id-link" data-item-id="{{ item.id }}">{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}</a> — [@if(!is_anonymized)<a id="a_username" data-username="{{ item.username || '' }}" @if(session && !session.is_anon) data-author-id="{{ item.author_id || '' }}" @endif href="/user/{{ item_username_lower }}" @if(session && !session.is_anon && item.author_id) tooltip="ID: {{ item.author_id }}" @endif @if(item.author_color) style="color: {{ item.author_color }}" @endif>{!! item.author_display_name || item.username || 'unknown' !!}</a>@else<span>anonymous</span>@endif] <span id="oc-badge-container">@if(item.is_oc) — <span class="oc-badge" tooltip="Original Content">OC</span>@endif</span>
|
||||
</span>
|
||||
<span id="hall-badge-container">@if(halls_enabled && item.primaryHall)<span class="badge hall-badge-wrap">
|
||||
<a href="/h/{{ item.primaryHall.slug }}" class="hall-badge-primary"><i class="fa-solid fa-layer-group"></i> {{ item.primaryHall.name }}</a>@if(item.otherHalls && item.otherHalls.length)<span class="hall-overflow-pill">+{{ item.otherHalls.length }}<span class="hall-overflow-tooltip">@each(item.otherHalls as oh)<a href="/h/{{ oh.slug }}">{{ oh.name }}</a>@endeach</span></span>@endif
|
||||
@@ -185,7 +187,7 @@
|
||||
@each(item.favorites as fav)
|
||||
@if(fav.hide_fav_badge && (!session || session.user !== fav.user))
|
||||
<a class="ghost-fav" tooltip="?" flow="up" style="cursor: default;"><img src="/s/img/ghost_fav.svg" style="height: 32px; width: 32px;" loading="lazy" /></a>
|
||||
@elseif(!session && guest_anonymize)
|
||||
@elseif(is_anonymized)
|
||||
<a class="ghost-fav" tooltip="anonymous" flow="up" style="cursor: default;"><img src="/a/default.png" style="height: 32px; width: 32px;" loading="lazy" /></a>
|
||||
@else
|
||||
<a href="/user/{{ fav.user.toLowerCase() }}" tooltip="{!! fav.display_name || fav.user !!}" flow="up"><img src="@if(fav.avatar_file)/a/{{ fav.avatar_file }}@elseif(fav.avatar)/t/{{ fav.avatar }}.webp@else/a/default.png@endif" style="height: 32px; width: 32px@if(fav.username_color); border-color: {{ fav.username_color }}@endif" loading="lazy" /></a>
|
||||
|
||||
+43
-4
@@ -942,6 +942,19 @@
|
||||
window.scrollerLoggedIn = @if(typeof session !== 'undefined' && session)true@else false@endif;
|
||||
window.scrollerIsMod = @if(typeof session !== 'undefined' && session && (session.admin || session.is_moderator))true@else false@endif;
|
||||
window.scrollerCsrf = "@if(typeof session !== 'undefined' && session){{ session.csrf_token || '' }}@else@endif";
|
||||
window.f0ckSession = {
|
||||
logged_in: window.scrollerLoggedIn,
|
||||
user: @if(typeof session !== 'undefined' && session && session.user)"{{ session.user }}"@else null@endif,
|
||||
is_anon: @if(typeof session !== 'undefined' && session && session.is_anon)true@else false@endif,
|
||||
anon_permissions: {{ anon_permissions_json || '{}' }},
|
||||
csrf_token: window.scrollerCsrf,
|
||||
guest_anonymize: @if(guest_anonymize) true @else false @endif,
|
||||
anon_anonymize: @if(anon_anonymize) true @else false @endif,
|
||||
is_anonymized: @if(is_anonymized) true @else false @endif,
|
||||
excluded_tags: @if(typeof session !== 'undefined' && session && session.excluded_tags) {{ JSON.stringify(session.excluded_tags) }} @else [] @endif,
|
||||
development: @if(development) true @else false @endif
|
||||
};
|
||||
window.f0ckDebug = window.f0ckSession.development ? console.log.bind(console) : () => {};
|
||||
window.scrollerEnableNsfl = {{ enable_nsfl ? 'true' : 'false' }};
|
||||
window.scrollerEnableSwf = {{ enable_swf ? 'true' : 'false' }};
|
||||
window.scrollerRuffleVolume = @if(typeof session !== 'undefined' && session && session.ruffle_volume !== undefined && session.ruffle_volume !== null){{ session.ruffle_volume }}@else 0.5@endif;
|
||||
@@ -1110,27 +1123,52 @@
|
||||
<button class="filter-reset-btn" id="filter-reset-btn">{{ t('scroller.reset_all') }}</button>
|
||||
</div>
|
||||
<div class="filter-scroll-area">
|
||||
@js
|
||||
const _isScrollerAnon = !(typeof session !== 'undefined' && session && session.user && !session.is_anon);
|
||||
const _scrollerAllowedModes = (typeof anon_permissions !== 'undefined' && anon_permissions && anon_permissions.allowed_modes) || ['sfw', 'nsfw', 'untagged', 'all', 'nsfl'];
|
||||
const _scrollerShowSfw = !_isScrollerAnon || _scrollerAllowedModes.includes('sfw');
|
||||
const _scrollerShowNsfw = (typeof session !== 'undefined' && session && !session.is_anon) || (_isScrollerAnon && _scrollerAllowedModes.includes('nsfw'));
|
||||
const _scrollerShowNsfl = enable_nsfl && ((typeof session !== 'undefined' && session && !session.is_anon) || (_isScrollerAnon && _scrollerAllowedModes.includes('nsfl')));
|
||||
const _scrollerShowAll = (typeof session !== 'undefined' && session && !session.is_anon) || (_isScrollerAnon && _scrollerAllowedModes.includes('all'));
|
||||
const _scrollerShowUntagged = (typeof session !== 'undefined' && session && (session.admin || session.is_moderator)) || (_isScrollerAnon && _scrollerAllowedModes.includes('untagged'));
|
||||
const _scrollerActiveModes = [_scrollerShowSfw ? 'sfw' : null, _scrollerShowNsfw ? 'nsfw' : null, _scrollerShowNsfl ? 'nsfl' : null, _scrollerShowUntagged ? 'untagged' : null].filter(Boolean);
|
||||
const _scrollerIsSingleMode = _isScrollerAnon && _scrollerActiveModes.length === 1 && !_scrollerShowAll;
|
||||
const _scrollerSingleMode = _scrollerIsSingleMode ? _scrollerActiveModes[0] : null;
|
||||
|
||||
const _scrollerAllowedMimes = (typeof anon_permissions !== 'undefined' && anon_permissions && anon_permissions.allowed_mimes) || ['image', 'video', 'audio', 'flash', 'pdf'];
|
||||
const _scrollerIsSingleMime = _isScrollerAnon && _scrollerAllowedMimes.length === 1;
|
||||
const _scrollerSingleMime = _scrollerIsSingleMime ? _scrollerAllowedMimes[0] : null;
|
||||
@endjs
|
||||
<div class="filter-section">
|
||||
<div class="filter-section-label">{{ t('scroller.rating') }}</div>
|
||||
<div class="pill-group" id="mode-pills">
|
||||
@if(_scrollerIsSingleMode)
|
||||
<button class="filter-pill active locked" data-mode="{{ _scrollerSingleMode === 'sfw' ? 0 : (_scrollerSingleMode === 'nsfw' ? 1 : (_scrollerSingleMode === 'nsfl' ? 4 : 2)) }}" style="cursor: not-allowed; pointer-events: none;" title="Locked by site permissions"><i class="fa-solid fa-lock" style="margin-right: 4px;"></i>{{ _scrollerSingleMode.toUpperCase() }}</button>
|
||||
@else
|
||||
@if(_scrollerShowSfw)
|
||||
<button class="filter-pill active" data-mode="0"><i class="fa-solid fa-shield-halved"></i>SFW</button>
|
||||
@if(session)
|
||||
@endif
|
||||
@if(_scrollerShowNsfw)
|
||||
<button class="filter-pill" data-mode="1"><i class="fa-solid fa-fire"></i>NSFW</button>
|
||||
@endif
|
||||
@if(session && enable_nsfl)
|
||||
@if(_scrollerShowNsfl)
|
||||
<button class="filter-pill" data-mode="4"><i class="fa-solid fa-skull"></i>NSFL</button>
|
||||
@endif
|
||||
@if(session)
|
||||
@if(_scrollerShowAll)
|
||||
<button class="filter-pill" data-mode="3">{{ t('scroller.all') }}</button>
|
||||
@endif
|
||||
@if(session && (session.admin || session.is_moderator))
|
||||
@if(_scrollerShowUntagged)
|
||||
<button class="filter-pill" data-mode="2">{{ t('scroller.untagged') }}</button>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-section">
|
||||
<div class="filter-section-label">{{ t('scroller.media_type') }}</div>
|
||||
<div class="pill-group" id="mime-pills">
|
||||
@if(_scrollerIsSingleMime)
|
||||
<button class="filter-pill active locked" data-mime="{{ _scrollerSingleMime }}" style="cursor: not-allowed; pointer-events: none;" title="Locked by site permissions"><i class="fa-solid fa-lock" style="margin-right: 4px;"></i>{{ t('scroller.' + _scrollerSingleMime) || _scrollerSingleMime }}</button>
|
||||
@else
|
||||
<button class="filter-pill active" data-mime=""><i class="fa-solid fa-layer-group"></i>{{ t('scroller.all') }}</button>
|
||||
@if(scroller_mime_cats.includes('video'))
|
||||
<button class="filter-pill" data-mime="video"><i class="fa-solid fa-film"></i>{{ t('scroller.video') }}</button>
|
||||
@@ -1141,6 +1179,7 @@
|
||||
@if(scroller_mime_cats.includes('audio'))
|
||||
<button class="filter-pill" data-mime="audio"><i class="fa-solid fa-music"></i>{{ t('scroller.audio') }}</button>
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<div class="filter-section">
|
||||
|
||||
@@ -1,40 +1,75 @@
|
||||
<div id="excluded-tags-overlay" style="display: none;">
|
||||
<div id="excluded-tags-close">×</div>
|
||||
<div class="search-container">
|
||||
@js
|
||||
const _isAnonUser = !(typeof session !== 'undefined' && session && session.user && !session.is_anon);
|
||||
const _canExcludeTags = !_isAnonUser || (typeof anon_permissions !== 'undefined' && anon_permissions && anon_permissions.exclude_tags !== false && anon_permissions.filter !== false);
|
||||
const _allowedModes = (typeof anon_permissions !== 'undefined' && anon_permissions && anon_permissions.allowed_modes) || ['sfw', 'nsfw', 'untagged', 'all', 'nsfl'];
|
||||
const _showSfw = !_isAnonUser || _allowedModes.includes('sfw');
|
||||
const _showNsfw = !_isAnonUser || _allowedModes.includes('nsfw');
|
||||
const _showNsfl = enable_nsfl && (!_isAnonUser || _allowedModes.includes('nsfl'));
|
||||
const _showUntagged = (typeof session !== 'undefined' && session && (session.admin || session.is_moderator)) || (_isAnonUser && _allowedModes.includes('untagged'));
|
||||
const _showAll = !_isAnonUser || _allowedModes.includes('all');
|
||||
|
||||
const _activeRatingList = [_showSfw ? 'sfw' : null, _showNsfw ? 'nsfw' : null, _showNsfl ? 'nsfl' : null, _showUntagged ? 'untagged' : null].filter(Boolean);
|
||||
const _isSingleRating = _isAnonUser && _activeRatingList.length === 1 && !_showAll;
|
||||
const _singleRating = _isSingleRating ? _activeRatingList[0] : null;
|
||||
|
||||
const _allowedMimes = (typeof anon_permissions !== 'undefined' && anon_permissions && anon_permissions.allowed_mimes) || ['image', 'video', 'audio', 'flash', 'pdf'];
|
||||
const _isSingleMime = _isAnonUser && _allowedMimes.length === 1;
|
||||
const _singleMime = _isSingleMime ? _allowedMimes[0] : null;
|
||||
const _hasMimeCats = typeof scroller_mime_cats !== 'undefined' && Array.isArray(scroller_mime_cats);
|
||||
const _showVideo = _hasMimeCats && scroller_mime_cats.includes('video') && (!_isAnonUser || _allowedMimes.includes('video'));
|
||||
const _showAudio = _hasMimeCats && scroller_mime_cats.includes('audio') && (!_isAnonUser || _allowedMimes.includes('audio'));
|
||||
const _showImage = _hasMimeCats && scroller_mime_cats.includes('image') && (!_isAnonUser || _allowedMimes.includes('image'));
|
||||
const _showFlash = enable_swf && (!_isAnonUser || _allowedMimes.includes('flash'));
|
||||
@endjs
|
||||
@if(_canExcludeTags)
|
||||
<div class="excluded-tags-section-header">
|
||||
<span class="excluded-tags-section-title"><i class="fa-solid fa-ban"></i> {{ t('filter.excluded_tags') || 'Excluded Tags' }}</span>
|
||||
<span id="nav_excluded_tags_count" class="badge badge-danger excluded-tags-count-badge" style="display: none;">0</span>
|
||||
</div>
|
||||
<div id="nav_excluded_tags_list"></div>
|
||||
<div class="nav-exclude">
|
||||
<input type="text" id="nav_exclude_tag_input" class="input" placeholder="{{ t('filter.tag_placeholder') }}" autocomplete="off" enterkeyhint="enter">
|
||||
<div id="nav_exclude_suggestions" class="tag-suggestions"></div>
|
||||
</div>
|
||||
@endif
|
||||
<!-- modes -->
|
||||
<div class="mode-filter">
|
||||
<button id="nav-shuffle-btn" class="shuffle-btn" title="Random Mode — shuffle all items"><span class="shuffle-icon">( - _ - )</span> {{ t('filter.random_mode') }}</button>
|
||||
<div class="rating-selector" id="rating-selector">
|
||||
<button class="rating-toggle-btn" data-rating="sfw" id="rating-btn-sfw" title="SFW">SFW</button>
|
||||
<button class="rating-toggle-btn" data-rating="nsfw" id="rating-btn-nsfw" title="NSFW">NSFW</button>
|
||||
@if(enable_nsfl)
|
||||
<button class="rating-toggle-btn" data-rating="nsfl" id="rating-btn-nsfl" title="NSFL">NSFL</button>
|
||||
<button id="nav-shuffle-btn" class="shuffle-btn" title="Random Mode — shuffle all items"@if(_isSingleRating) disabled style="cursor: not-allowed; opacity: 0.6;" title="Locked by site permissions"@endif><span class="shuffle-icon">( - _ - )</span> {{ t('filter.random_mode') }}</button>
|
||||
<div class="rating-selector @if(_isSingleRating) locked @endif" id="rating-selector" @if(_isSingleRating) data-locked-rating="{{ _singleRating }}" @endif>
|
||||
@if(_showSfw)
|
||||
<button class="rating-toggle-btn @if(_isSingleRating && _singleRating === 'sfw') active locked @endif" data-rating="sfw" id="rating-btn-sfw" title="SFW"@if(_isSingleRating) disabled style="cursor: not-allowed; opacity: 0.85;"@endif>SFW@if(_isSingleRating && _singleRating === 'sfw')<i class="fa-solid fa-lock" style="margin-left: 5px; font-size: 0.75em; opacity: 0.7;"></i>@endif</button>
|
||||
@endif
|
||||
@if(session && session.admin || session && session.is_moderator)
|
||||
<button class="rating-toggle-btn" data-rating="untagged" id="rating-btn-untagged" title="Untagged">UNT</button>
|
||||
@if(_showNsfw)
|
||||
<button class="rating-toggle-btn @if(_isSingleRating && _singleRating === 'nsfw') active locked @endif" data-rating="nsfw" id="rating-btn-nsfw" title="NSFW"@if(_isSingleRating) disabled style="cursor: not-allowed; opacity: 0.85;"@endif>NSFW@if(_isSingleRating && _singleRating === 'nsfw')<i class="fa-solid fa-lock" style="margin-left: 5px; font-size: 0.75em; opacity: 0.7;"></i>@endif</button>
|
||||
@endif
|
||||
@if(_showNsfl)
|
||||
<button class="rating-toggle-btn @if(_isSingleRating && _singleRating === 'nsfl') active locked @endif" data-rating="nsfl" id="rating-btn-nsfl" title="NSFL"@if(_isSingleRating) disabled style="cursor: not-allowed; opacity: 0.85;"@endif>NSFL@if(_isSingleRating && _singleRating === 'nsfl')<i class="fa-solid fa-lock" style="margin-left: 5px; font-size: 0.75em; opacity: 0.7;"></i>@endif</button>
|
||||
@endif
|
||||
@if(_showUntagged)
|
||||
<button class="rating-toggle-btn @if(_isSingleRating && _singleRating === 'untagged') active locked @endif" data-rating="untagged" id="rating-btn-untagged" title="Untagged"@if(_isSingleRating) disabled style="cursor: not-allowed; opacity: 0.85;"@endif>UNT@if(_isSingleRating && _singleRating === 'untagged')<i class="fa-solid fa-lock" style="margin-left: 5px; font-size: 0.75em; opacity: 0.7;"></i>@endif</button>
|
||||
@endif
|
||||
@if(_showAll && !_isSingleRating)
|
||||
<button class="rating-toggle-btn rating-toggle-all" id="rating-btn-all" title="Show All Ratings">ALL</button>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<!-- mimes -->
|
||||
@if(show_mime_picker)
|
||||
<div class="nav-mime-filter nav-mime-menu" id="nav-mime-menu">
|
||||
@if(scroller_mime_cats && scroller_mime_cats.includes('video'))
|
||||
<label class="nav-mime-item"><input type="checkbox" value="video" id="mime-video">{{ t('filter.video') }}</label>
|
||||
<div class="nav-mime-filter nav-mime-menu" id="nav-mime-menu" @if(_isSingleMime) data-locked-mime="{{ _singleMime }}" @endif>
|
||||
@if(_showVideo)
|
||||
<label class="nav-mime-item @if(_isSingleMime) locked @endif" @if(_isSingleMime) style="cursor: not-allowed; opacity: 0.85;" title="Locked by site permissions" @endif><input type="checkbox" value="video" id="mime-video" @if(_isSingleMime) checked disabled @endif>{{ t('filter.video') }}@if(_isSingleMime)<i class="fa-solid fa-lock" style="margin-left: 6px; font-size: 0.8em; opacity: 0.7;"></i>@endif</label>
|
||||
@endif
|
||||
@if(scroller_mime_cats && scroller_mime_cats.includes('audio'))
|
||||
<label class="nav-mime-item"><input type="checkbox" value="audio" id="mime-audio">{{ t('filter.audio') }}</label>
|
||||
@if(_showAudio)
|
||||
<label class="nav-mime-item @if(_isSingleMime) locked @endif" @if(_isSingleMime) style="cursor: not-allowed; opacity: 0.85;" title="Locked by site permissions" @endif><input type="checkbox" value="audio" id="mime-audio" @if(_isSingleMime) checked disabled @endif>{{ t('filter.audio') }}@if(_isSingleMime)<i class="fa-solid fa-lock" style="margin-left: 6px; font-size: 0.8em; opacity: 0.7;"></i>@endif</label>
|
||||
@endif
|
||||
@if(scroller_mime_cats && scroller_mime_cats.includes('image'))
|
||||
<label class="nav-mime-item"><input type="checkbox" value="image" id="mime-image">{{ t('filter.image') }}</label>
|
||||
@if(_showImage)
|
||||
<label class="nav-mime-item @if(_isSingleMime) locked @endif" @if(_isSingleMime) style="cursor: not-allowed; opacity: 0.85;" title="Locked by site permissions" @endif><input type="checkbox" value="image" id="mime-image" @if(_isSingleMime) checked disabled @endif>{{ t('filter.image') }}@if(_isSingleMime)<i class="fa-solid fa-lock" style="margin-left: 6px; font-size: 0.8em; opacity: 0.7;"></i>@endif</label>
|
||||
@endif
|
||||
@if(enable_swf)
|
||||
<label class="nav-mime-item"><input type="checkbox" value="flash" id="mime-flash">{{ t('filter.flash') }}</label>
|
||||
@if(_showFlash)
|
||||
<label class="nav-mime-item @if(_isSingleMime) locked @endif" @if(_isSingleMime) style="cursor: not-allowed; opacity: 0.85;" title="Locked by site permissions" @endif><input type="checkbox" value="flash" id="mime-flash" @if(_isSingleMime) checked disabled @endif>{{ t('filter.flash') }}@if(_isSingleMime)<i class="fa-solid fa-lock" style="margin-left: 6px; font-size: 0.8em; opacity: 0.7;"></i>@endif</label>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@@ -178,7 +178,7 @@
|
||||
@endif
|
||||
@if(private_society && !session)
|
||||
<script>
|
||||
window.f0ckSession = { logged_in: false, onara: @if(onara) true @else false @endif, enable_item_slugs: @if(enable_item_slugs) true @else false @endif, enable_anonymous_access: @if(enable_anonymous_access) true @else false @endif, hide_comments_from_public: @if(hide_comments_from_public) true @else false @endif, guest_anonymize: @if(guest_anonymize) true @else false @endif, enable_xd_score: @if(enable_xd_score) true @else false @endif, default_theme: "{{ default_theme }}", show_content_warning: @if(show_content_warning) true @else false @endif, use_new_layout: @if(default_layout === 'legacy')false @else true @endif, comment_display_mode: {{ comment_display_mode }}, comment_max_length: {{ comment_max_length !== null && comment_max_length !== undefined ? comment_max_length : 'null' }}, development: @if(development) true @else false @endif, allow_comment_deletion: @if(allow_comment_deletion) true @else false @endif, hide_sidebar_default: @if(hide_sidebar_default) true @else false @endif, public_untagged: @if(public_untagged) true @else false @endif, public_nsfw: @if(public_nsfw) true @else false @endif };
|
||||
window.f0ckSession = { logged_in: false, is_anon: false, excluded_tags: [], onara: @if(onara) true @else false @endif, enable_item_slugs: @if(enable_item_slugs) true @else false @endif, enable_anonymous_access: @if(enable_anonymous_access) true @else false @endif, anon_permissions: {{ anon_permissions_json || '{}' }}, hide_comments_from_public: @if(hide_comments_from_public) true @else false @endif, guest_anonymize: @if(guest_anonymize) true @else false @endif, anon_anonymize: @if(anon_anonymize) true @else false @endif, is_anonymized: @if(is_anonymized) true @else false @endif, enable_xd_score: @if(enable_xd_score) true @else false @endif, default_theme: "{{ default_theme }}", show_content_warning: @if(show_content_warning) true @else false @endif, use_new_layout: @if(default_layout === 'legacy')false @else true @endif, comment_display_mode: {{ comment_display_mode }}, comment_max_length: {{ comment_max_length !== null && comment_max_length !== undefined ? comment_max_length : 'null' }}, development: @if(development) true @else false @endif, allow_comment_deletion: @if(allow_comment_deletion) true @else false @endif, hide_sidebar_default: @if(hide_sidebar_default) true @else false @endif, public_untagged: @if(public_untagged) true @else false @endif, public_nsfw: @if(public_nsfw) true @else false @endif };
|
||||
window.f0ckDebug = window.f0ckSession.development ? console.log.bind(console) : () => {};
|
||||
window.f0ckDefaultThumbSize = "{{ default_thumb_size }}";
|
||||
(() => {
|
||||
@@ -422,10 +422,14 @@
|
||||
public_nsfw: @if(public_nsfw) true @else false @endif,
|
||||
enable_item_slugs: @if(enable_item_slugs) true @else false @endif,
|
||||
enable_anonymous_access: @if(enable_anonymous_access) true @else false @endif,
|
||||
anon_permissions: {{ anon_permissions_json || '{}' }},
|
||||
strict_mode: @if(session && session.strict_mode) true @else false @endif,
|
||||
logged_in: @if(session) true @else false @endif,
|
||||
excluded_tags: @if(session && session.excluded_tags) {{ JSON.stringify(session.excluded_tags) }} @else [] @endif,
|
||||
hide_comments_from_public: @if(hide_comments_from_public) true @else false @endif,
|
||||
guest_anonymize: @if(guest_anonymize) true @else false @endif,
|
||||
anon_anonymize: @if(anon_anonymize) true @else false @endif,
|
||||
is_anonymized: @if(is_anonymized) true @else false @endif,
|
||||
use_new_layout: @if(session)@if(session.use_new_layout) true @else false @endif@else @if(default_layout === 'legacy')false @else true @endif@endif,
|
||||
csrf_token: "{{ csrf_token }}",
|
||||
default_theme: "{{ default_theme }}",
|
||||
@@ -477,7 +481,7 @@
|
||||
allow_comment_deletion: @if(allow_comment_deletion) true @else false @endif,
|
||||
enable_comment_polls: @if(enable_comment_polls) true @else false @endif,
|
||||
hide_sidebar_default: @if(hide_sidebar_default) true @else false @endif,
|
||||
mode: {{ mode !== undefined ? mode : 0 }}
|
||||
mode: {{ typeof mode !== 'undefined' ? mode : 0 }}
|
||||
};
|
||||
window.f0ckDebug = window.f0ckSession.development ? console.log.bind(console) : () => {};
|
||||
window.f0ckDefaultThumbSize = "{{ default_thumb_size }}";
|
||||
@@ -557,6 +561,11 @@
|
||||
zomg_on: "{{ t('toast.zomg_on') }}",
|
||||
zomg_off: "{{ t('toast.zomg_off') }}",
|
||||
copied: "{{ t('toast.copied') }}",
|
||||
exclude_tag: "{{ t('filter.exclude_tag') || 'Exclude tag' }}",
|
||||
unexclude_tag: "{{ t('filter.unexclude_tag') || 'Excluded (click to unexclude)' }}",
|
||||
no_tags_excluded: "{{ t('filter.no_tags_excluded') || 'No tags excluded yet' }}",
|
||||
tag_excluded_msg: "{{ t('filter.tag_excluded_msg') || 'Tag {tag} added to excluded tags' }}",
|
||||
tag_unexcluded_msg: "{{ t('filter.tag_unexcluded_msg') || 'Tag {tag} removed from excluded tags' }}",
|
||||
// actions
|
||||
fav_added: "{{ t('toast.fav_added') }}",
|
||||
fav_removed: "{{ t('toast.fav_removed') }}",
|
||||
|
||||
@@ -104,7 +104,7 @@
|
||||
<canvas class="hidden-xs" id="bg"></canvas>
|
||||
@endif
|
||||
@include(snippets/navbar)
|
||||
@if(session)
|
||||
@if(session || (enable_anonymous_access && anon_permissions.filter))
|
||||
@include(snippets/excluded-tags-modal)
|
||||
@endif
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
@each(items as item)
|
||||
<a href="{{ link.main }}{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}" class="{{ item.is_pinned ? 'anim-boxshadow ' : '' }}thumb lazy-thumb {{ item.has_notification ? 'has-notif' : '' }} {{ item.is_pinned ? 'is-pinned' : '' }}" data-file="{{ item.dest }}" data-mime="{{ item.mime }}" data-user="@if(session || !guest_anonymize){!! item.display_name || item.username !!}@else anonymous@endif" data-ext="{{ item.mime.split('/')[1].replace('youtube', 'yt').replace('x-shockwave-flash', 'flash').replace('vnd.adobe.flash.movie', 'flash').replace('x-zip-compressed', 'zip').replace('x-rar-compressed', 'rar').replace('vnd.rar', 'rar').replace('x-7z-compressed', '7z').replace('x-tar', 'tar').replace('x-bzip2', 'bz2').replace('x-xz', 'xz').toUpperCase() }}" data-mode="{{ item.tag_id == nsfl_tag_id ? 'nsfl' : (item.tag_id == 2 ? 'nsfw' : (item.tag_id == 1 ? 'sfw' : 'null')) }}" data-bg="/t/{{ item.id }}.webp" data-size="{{ enable_dynamic_thumbs ? (item.thumb_size || 1) : 1 }}">
|
||||
<a href="{{ link.main }}{{ (enable_item_slugs && item.slug) ? item.slug : item.id }}" class="{{ item.is_pinned ? 'anim-boxshadow ' : '' }}thumb lazy-thumb {{ item.has_notification ? 'has-notif' : '' }} {{ item.is_pinned ? 'is-pinned' : '' }}" data-file="{{ item.dest }}" data-mime="{{ item.mime }}" data-user="@if(!is_anonymized){!! item.display_name || item.username !!}@else anonymous@endif" data-ext="{{ item.mime.split('/')[1].replace('youtube', 'yt').replace('x-shockwave-flash', 'flash').replace('vnd.adobe.flash.movie', 'flash').replace('x-zip-compressed', 'zip').replace('x-rar-compressed', 'rar').replace('vnd.rar', 'rar').replace('x-7z-compressed', '7z').replace('x-tar', 'tar').replace('x-bzip2', 'bz2').replace('x-xz', 'xz').toUpperCase() }}" data-mode="{{ item.tag_id == nsfl_tag_id ? 'nsfl' : (item.tag_id == 2 ? 'nsfw' : (item.tag_id == 1 ? 'sfw' : 'null')) }}" data-bg="/t/{{ item.id }}.webp" data-size="{{ enable_dynamic_thumbs ? (item.thumb_size || 1) : 1 }}">
|
||||
<div class="thumb-indicators">
|
||||
@if(item.is_pinned)
|
||||
<i class="fa-solid fa-thumbtack pin-indicator anim"></i>
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
<!-- Nav links: desktop always-on, mobile hidden until toggled -->
|
||||
<div class="nav-collapse" id="navbarContent">
|
||||
<div class="nav-links">
|
||||
@if(!session.is_anon)
|
||||
@if(!session.is_anon || (enable_anonymous_access && anon_permissions.upload))
|
||||
<a id="nav-upload-link" style="cursor:pointer;"><i class="fa-solid fa-angle-up"></i> {{ t('nav.upload') }}</a>
|
||||
@endif
|
||||
@if(meme_creator)
|
||||
@@ -118,6 +118,7 @@
|
||||
<a href="/settings" title="Settings" class="desktop-only"><i class="fa-solid fa-gear"></i></a>
|
||||
|
||||
<!-- Filter -->
|
||||
@if(!session.is_anon || (enable_anonymous_access && anon_permissions.filter))
|
||||
<a href="#" id="nav-filter-btn" title="Filter" style="position: relative; display: inline-flex; align-items: center; justify-content: center; z-index: 1000;">
|
||||
<i class="fa-solid fa-filter"></i>
|
||||
@if(session.mode == 0)
|
||||
@@ -134,6 +135,7 @@
|
||||
<span id="nav-filter-badge" class="filter-badge filter-badge-sfw">SFW</span>
|
||||
@endif
|
||||
</a>
|
||||
@endif
|
||||
|
||||
<!-- Logout -->
|
||||
<a href="/logout" title="Logout" class="desktop-only"><i class="fa-solid fa-right-from-bracket"></i></a>
|
||||
@@ -267,6 +269,12 @@
|
||||
<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>
|
||||
@if(enable_anonymous_access && anon_permissions.filter)
|
||||
<a href="#" id="nav-filter-btn" title="Filter" style="position: relative; display: inline-flex; align-items: center; justify-content: center; z-index: 1000;">
|
||||
<i class="fa-solid fa-filter"></i>
|
||||
<span id="nav-filter-badge" class="filter-badge filter-badge-sfw">SFW</span>
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user