updating from dev
This commit is contained in:
223
views/admin/chat.html
Normal file
223
views/admin/chat.html
Normal file
@@ -0,0 +1,223 @@
|
||||
@include(snippets/header)
|
||||
<div class="pagewrapper">
|
||||
<div id="main">
|
||||
<div class="container">
|
||||
<h1>ADMINBEREICH</h1>
|
||||
<h5>Global Chat Management</h5>
|
||||
<a href="/admin" style="font-size: 0.8em; color: var(--accent); text-decoration: none;"><i class="fa-solid fa-arrow-left"></i> Back to Dashboard</a>
|
||||
<hr>
|
||||
|
||||
<div class="chat-cheatsheet" style="background: rgba(0,0,0,0.2); padding: 15px; border-radius: 4px; margin-bottom: 20px;">
|
||||
<label style="display: block; font-weight: bold; color: var(--accent); margin-bottom: 10px;">Global Chat Cheat Sheet</label>
|
||||
<p style="font-size: 0.85em; color: #ccc; margin-bottom: 10px;">Admin commands for the global chat widget.</p>
|
||||
<table style="width: 100%; font-size: 0.85em; border-collapse: collapse;">
|
||||
<tr style="border-bottom: 1px solid rgba(255,255,255,0.05);">
|
||||
<th style="text-align: left; padding: 8px 0; color: #fff;">Command</th>
|
||||
<th style="text-align: left; padding: 8px 0; color: #fff;">Description</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0;"><code style="color: var(--accent);">/clear</code></td>
|
||||
<td style="padding: 8px 0;">Deletes all messages and restarts history.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0;"><code style="color: var(--accent);">/setbackground <url> [opts]</code></td>
|
||||
<td style="padding: 8px 0;">Sets a background image. Opts: <code style="color: #aaa;">center / cover no-repeat</code> etc.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0;"><code style="color: var(--accent);">/clearbg</code></td>
|
||||
<td style="padding: 8px 0;">Removes the chat background.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0;"><code style="color: var(--accent);">/settopic <text></code></td>
|
||||
<td style="padding: 8px 0;">Sets the pinned topic at the top of the chat.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td style="padding: 8px 0;"><code style="color: var(--accent);">/cleartopic</code></td>
|
||||
<td style="padding: 8px 0;">Removes the pinned topic.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="chat-bg-tool" style="background: rgba(0,0,0,0.2); padding: 15px; border-radius: 4px; margin-bottom: 20px;">
|
||||
<label style="display: block; font-weight: bold; color: var(--accent); margin-bottom: 10px;">Chat Background Builder</label>
|
||||
<p style="font-size: 0.85em; color: #ccc; margin-bottom: 10px;">Craft the perfect background command or apply it instantly.</p>
|
||||
<div style="display: flex; flex-direction: column; gap: 10px;">
|
||||
<input type="text" id="bg_url" placeholder="Image URL (must be from allowed host)" style="width: 100%; background: #333; border: 1px solid #444; color: #fff; padding: 8px; border-radius: 4px; outline: none;">
|
||||
|
||||
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||
<select id="bg_pos" style="background: #333; border: 1px solid #444; color: #fff; padding: 8px; border-radius: 4px; cursor: pointer; outline: none;">
|
||||
<option value="center">Position: Center</option>
|
||||
<option value="top">Position: Top</option>
|
||||
<option value="bottom">Position: Bottom</option>
|
||||
<option value="left">Position: Left</option>
|
||||
<option value="right">Position: Right</option>
|
||||
<option value="top left">Position: Top Left</option>
|
||||
<option value="top right">Position: Top Right</option>
|
||||
<option value="bottom left">Position: Bottom Left</option>
|
||||
<option value="bottom right">Position: Bottom Right</option>
|
||||
</select>
|
||||
|
||||
<div style="display: flex; gap: 5px; flex-grow: 1; min-width: 150px;">
|
||||
<select id="bg_size_presets" onchange="document.getElementById('bg_size').value = this.value; updateBgCommand();" style="background: #333; border: 1px solid #444; color: #fff; padding: 8px; border-radius: 4px 0 0 4px; cursor: pointer; outline: none; flex-shrink: 0;">
|
||||
<option value="cover">Size: Cover</option>
|
||||
<option value="contain">Size: Contain</option>
|
||||
<option value="auto">Size: Auto</option>
|
||||
<option value="100% 100%">Size: Stretch</option>
|
||||
<option value="">Custom...</option>
|
||||
</select>
|
||||
<input type="text" id="bg_size" value="cover" placeholder="e.g. 50% or 200px" style="background: #333; border: 1px solid #444; border-left: 0; color: #fff; padding: 8px; border-radius: 0 4px 4px 0; outline: none; flex-grow: 1;" oninput="updateBgCommand()">
|
||||
</div>
|
||||
|
||||
<select id="bg_repeat" style="background: #333; border: 1px solid #444; color: #fff; padding: 8px; border-radius: 4px; cursor: pointer; outline: none;">
|
||||
<option value="no-repeat">Repeat: No</option>
|
||||
<option value="repeat">Repeat: Yes</option>
|
||||
<option value="repeat-x">Repeat: X only</option>
|
||||
<option value="repeat-y">Repeat: Y only</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; align-items: center; gap: 10px; background: rgba(0,0,0,0.3); padding: 10px; border-radius: 4px; font-family: monospace; font-size: 0.9em;">
|
||||
<span style="color: #888; white-space: nowrap;">Command:</span>
|
||||
<code id="bg_command_output" style="color: var(--accent); white-space: nowrap; overflow-x: auto; flex-grow: 1;">/setbackground center / cover no-repeat</code>
|
||||
<button onclick="copyBgCommand()" style="background: #444; border: 0; color: #fff; padding: 4px 12px; border-radius: 4px; cursor: pointer; font-size: 0.8em; font-weight: bold; transition: background 0.2s;" onmouseover="this.style.background='#555'" onmouseout="this.style.background='#444'">Copy</button>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<button onclick="applyBgFromAdmin()" id="apply_bg_btn" style="flex-grow: 1; background: var(--accent); border: 0; color: #000; padding: 12px; border-radius: 4px; cursor: pointer; font-weight: bold; transition: opacity 0.2s;" onmouseover="this.style.opacity='0.9'" onmouseout="this.style.opacity='1'">Apply Instantly</button>
|
||||
<button onclick="clearBgFromAdmin()" style="background: #d9534f; border: 0; color: #fff; padding: 12px 20px; border-radius: 4px; cursor: pointer; font-weight: bold; transition: opacity 0.2s;" onmouseover="this.style.opacity='0.9'" onmouseout="this.style.opacity='1'">Clear Background</button>
|
||||
</div>
|
||||
</div>
|
||||
<span id="chat-status" style="display: block; margin-top: 10px; font-size: 0.8em; font-weight: bold; text-align: right;"></span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Chat Background Builder Logic
|
||||
window.builder_url = "";
|
||||
window.builder_pos = "";
|
||||
window.builder_size = "";
|
||||
window.builder_repeat = "";
|
||||
window.builder_opts = "";
|
||||
window.builder_cmd = "";
|
||||
|
||||
function updateBgCommand() {
|
||||
var builder_uEl = document.getElementById('bg_url');
|
||||
var builder_pEl = document.getElementById('bg_pos');
|
||||
var builder_sEl = document.getElementById('bg_size');
|
||||
var builder_rEl = document.getElementById('bg_repeat');
|
||||
var builder_oEl = document.getElementById('bg_command_output');
|
||||
|
||||
if (!builder_uEl || !builder_pEl || !builder_sEl || !builder_rEl || !builder_oEl) return { url: '', opts: '' };
|
||||
|
||||
window.builder_url = builder_uEl.value.trim();
|
||||
window.builder_pos = builder_pEl.value;
|
||||
window.builder_size = builder_sEl.value;
|
||||
window.builder_repeat = builder_rEl.value;
|
||||
|
||||
window.builder_opts = window.builder_pos + " / " + window.builder_size + " " + window.builder_repeat;
|
||||
window.builder_cmd = "/setbackground " + window.builder_url + " " + window.builder_opts;
|
||||
builder_oEl.textContent = window.builder_cmd;
|
||||
return { url: window.builder_url, opts: window.builder_opts };
|
||||
}
|
||||
|
||||
if (document.getElementById('bg_url')) {
|
||||
document.getElementById('bg_url').addEventListener('input', updateBgCommand);
|
||||
document.getElementById('bg_pos').addEventListener('change', updateBgCommand);
|
||||
document.getElementById('bg_size').addEventListener('change', updateBgCommand);
|
||||
document.getElementById('bg_repeat').addEventListener('change', updateBgCommand);
|
||||
}
|
||||
|
||||
function copyBgCommand() {
|
||||
const outputElem = document.getElementById('bg_command_output');
|
||||
if (!outputElem) return;
|
||||
const cmd = outputElem.textContent;
|
||||
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||
navigator.clipboard.writeText(cmd);
|
||||
const status = document.getElementById('chat-status');
|
||||
if (status) {
|
||||
status.textContent = 'Command copied to clipboard!';
|
||||
status.style.color = 'var(--accent)';
|
||||
setTimeout(() => { status.textContent = ''; }, 2000);
|
||||
}
|
||||
} else {
|
||||
alert('Clipboard access denied or not supported.');
|
||||
}
|
||||
}
|
||||
|
||||
async function applyBgFromAdmin() {
|
||||
const { url, opts } = updateBgCommand();
|
||||
if (!url) return alert('Please enter an image URL first.');
|
||||
|
||||
const btn = document.getElementById('apply_bg_btn');
|
||||
const status = document.getElementById('chat-status');
|
||||
if (btn) {
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Applying...';
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/chat/background', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-CSRF-Token': '{{ csrf_token }}'
|
||||
},
|
||||
body: new URLSearchParams({ url, opts }).toString()
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
if (status) {
|
||||
status.textContent = 'Background applied successfully!';
|
||||
status.style.color = '#28a745';
|
||||
}
|
||||
if (btn) btn.textContent = 'Applied!';
|
||||
setTimeout(() => {
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Apply Instantly';
|
||||
}
|
||||
if (status) status.textContent = '';
|
||||
}, 3000);
|
||||
} else {
|
||||
throw new Error(data.msg || 'Failed to apply background');
|
||||
}
|
||||
} catch (err) {
|
||||
alert(err.message);
|
||||
if (btn) {
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Apply Instantly';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function clearBgFromAdmin() {
|
||||
if (!confirm('Are you sure you want to clear the chat background?')) return;
|
||||
const status = document.getElementById('chat-status');
|
||||
try {
|
||||
const res = await fetch('/api/chat/background', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'X-CSRF-Token': '{{ csrf_token }}'
|
||||
},
|
||||
body: new URLSearchParams({}).toString()
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
if (status) {
|
||||
status.textContent = 'Background cleared!';
|
||||
status.style.color = '#28a745';
|
||||
}
|
||||
const urlInput = document.getElementById('bg_url');
|
||||
if (urlInput) urlInput.value = '';
|
||||
updateBgCommand();
|
||||
setTimeout(() => { if (status) status.textContent = ''; }, 3000);
|
||||
}
|
||||
} catch (err) { alert(err.message); }
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
@include(snippets/footer)
|
||||
@@ -39,7 +39,7 @@
|
||||
<script>
|
||||
(() => {
|
||||
var i18n = window.f0ckI18n || {};
|
||||
console.log('[MEME_ADMIN] Initializing');
|
||||
window.f0ckDebug('[MEME_ADMIN] Initializing');
|
||||
const esc = (s) => (s || '').toString().replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
||||
const loadMemes = async () => {
|
||||
try {
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
const addMeme = async (e) => {
|
||||
if (e) e.preventDefault();
|
||||
console.log('[MEME_ADMIN] addMeme triggered');
|
||||
window.f0ckDebug('[MEME_ADMIN] addMeme triggered');
|
||||
|
||||
const template_id = document.getElementById('meme-id').value;
|
||||
const name = document.getElementById('meme-name').value;
|
||||
@@ -145,7 +145,7 @@
|
||||
|
||||
const btnAddMeme = document.getElementById('add-meme');
|
||||
if (btnAddMeme) {
|
||||
console.log('[MEME_ADMIN] Registering click listener');
|
||||
window.f0ckDebug('[MEME_ADMIN] Registering click listener');
|
||||
btnAddMeme.addEventListener('click', addMeme);
|
||||
} else {
|
||||
console.error('[MEME_ADMIN] Add button not found!');
|
||||
|
||||
@@ -27,10 +27,10 @@
|
||||
<script>
|
||||
const loadTokens = async () => {
|
||||
try {
|
||||
console.log('Loading tokens...');
|
||||
window.f0ckDebug('Loading tokens...');
|
||||
const res = await fetch('/api/v2/admin/tokens');
|
||||
const data = await res.json();
|
||||
console.log('Tokens data:', data);
|
||||
window.f0ckDebug('Tokens data:', data);
|
||||
if (data.success) {
|
||||
const tbody = document.getElementById('token-list');
|
||||
if (!tbody) return;
|
||||
@@ -57,14 +57,14 @@
|
||||
};
|
||||
|
||||
const generateToken = async () => {
|
||||
console.log('Generating...');
|
||||
window.f0ckDebug('Generating...');
|
||||
try {
|
||||
const res = await fetch('/api/v2/admin/tokens/create', {
|
||||
method: 'POST',
|
||||
headers: { 'X-CSRF-Token': window.f0ckSession?.csrf_token }
|
||||
});
|
||||
const data = await res.json();
|
||||
console.log('Gen result:', data);
|
||||
window.f0ckDebug('Gen result:', data);
|
||||
if (data.success) {
|
||||
loadTokens();
|
||||
} else {
|
||||
|
||||
@@ -313,6 +313,57 @@
|
||||
}, { hideReason: false, allowEmpty: true, confirmText: 'Set Nick', placeholder: currentDisplay || 'e.g. F.O.O' });
|
||||
}
|
||||
|
||||
async function adminLockLayout(btn) {
|
||||
var id = btn.dataset.id;
|
||||
var userName = btn.dataset.name;
|
||||
var isLocked = btn.dataset.locked === '1';
|
||||
var currentMode = btn.dataset.mode || '0';
|
||||
|
||||
if (isLocked) {
|
||||
ModAction.confirm('Unlock Layout', 'Unlock comment layout for <strong>' + escHTML(userName) + '</strong>? They will be able to change it again.', async () => {
|
||||
var res = await fetch('/api/v2/admin/users/lock-layout', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ user_id: id, lock: false })
|
||||
});
|
||||
var data = await res.json();
|
||||
if (data.success) {
|
||||
showFlash('Layout unlocked for ' + escHTML(userName), 'success');
|
||||
btn.dataset.locked = '0';
|
||||
btn.innerHTML = '<i class="fa fa-lock"></i> Lock';
|
||||
btn.title = 'Lock Layout';
|
||||
} else {
|
||||
throw new Error(data.msg || 'Failed to unlock layout');
|
||||
}
|
||||
}, { hideReason: true });
|
||||
} else {
|
||||
var hint = 'Select comment display mode to force for <strong>' + escHTML(userName) + '</strong>:<br><br>' +
|
||||
'<select id="force-mode-select" class="input" style="width: 100%; padding: 8px;">' +
|
||||
'<option value="0" ' + (currentMode == '0' ? 'selected' : '') + '>Tree</option>' +
|
||||
'<option value="1" ' + (currentMode == '1' ? 'selected' : '') + '>Linear</option>' +
|
||||
'</select>';
|
||||
|
||||
ModAction.confirm('Lock Layout', hint, async () => {
|
||||
var mode = document.getElementById('force-mode-select').value;
|
||||
var res = await fetch('/api/v2/admin/users/lock-layout', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ user_id: id, lock: true, mode: mode })
|
||||
});
|
||||
var data = await res.json();
|
||||
if (data.success) {
|
||||
showFlash('Layout locked to ' + (mode == '0' ? 'Tree' : 'Linear') + ' for ' + escHTML(userName), 'success');
|
||||
btn.dataset.locked = '1';
|
||||
btn.dataset.mode = mode;
|
||||
btn.innerHTML = '<i class="fa fa-lock-open"></i> Unlock';
|
||||
btn.title = 'Unlock Layout';
|
||||
} else {
|
||||
throw new Error(data.msg || 'Failed to lock layout');
|
||||
}
|
||||
}, { hideReason: true, confirmText: 'Lock & Apply' });
|
||||
}
|
||||
}
|
||||
|
||||
var currentPage = {!! page !!};
|
||||
var hasMore = {!! hasMore ? 'true' : 'false' !!};
|
||||
var isLoading = false;
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
@if(u.id && u.login !== 'deleted_user')
|
||||
<button data-id="{{ u.id }}" data-name="{!! u.user !!}" data-username="{{ u.login }}" data-display="{!! u.display_name || '' !!}" onclick="adminSetDisplayName(this)" class="btn-modern btn-nick" style="background: rgba(100, 200, 100, 0.1); color: #64c864; border: 1px solid rgba(100, 200, 100, 0.2);" title="Set stylized display name">✏ Nick</button>
|
||||
<button data-id="{{ u.id }}" data-name="{!! u.user !!}" data-username="{{ u.login }}" onclick="adminSetPassword(this)" class="btn-modern btn-pw" style="background: rgba(var(--accent-rgb, 0, 150, 255), 0.1); color: var(--accent, #0096ff); border: 1px solid rgba(var(--accent-rgb, 0, 150, 255), 0.2);">Set PW</button>
|
||||
<button data-id="{{ u.id }}" data-name="{!! u.user !!}" data-username="{{ u.login }}" data-locked="{{ u.force_comment_display_mode }}" data-mode="{{ u.comment_display_mode }}" onclick="adminLockLayout(this)" class="btn-modern" style="background: rgba(255, 100, 0, 0.1); color: #ff6400; border: 1px solid rgba(255, 100, 0, 0.2);" title="{{ u.force_comment_display_mode ? 'Unlock Layout' : 'Lock Layout' }}"><i class="fa fa-{{ u.force_comment_display_mode ? 'lock-open' : 'lock' }}"></i> {{ u.force_comment_display_mode ? 'Unlock' : 'Lock' }}</button>
|
||||
<button data-id="{{ u.id }}" data-name="{!! u.user !!}" data-username="{{ u.login }}" onclick="adminDeleteUser(this)" class="btn-modern btn-delete" style="background: rgba(217, 83, 79, 0.1); color: #d9534f; border: 1px solid rgba(217, 83, 79, 0.2);">Delete</button>
|
||||
@elseif(u.login === 'deleted_user')
|
||||
<span style="font-size: 0.8rem; color: #666; font-style: italic; padding: 5px 10px;">Protected System Account</span>
|
||||
|
||||
Reference in New Issue
Block a user