feat: Actualizar la tipografía y mejorar la selección de roles en el panel de configuración
This commit is contained in:
@@ -76,7 +76,7 @@ body::before {
|
|||||||
|
|
||||||
/* Tipografía Moderna */
|
/* Tipografía Moderna */
|
||||||
h1 {
|
h1 {
|
||||||
font-family: 'Press Start 2P', monospace;
|
font-family: 'BoldPixels', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial;
|
||||||
font-size: clamp(2rem, 5vw, 4rem);
|
font-size: clamp(2rem, 5vw, 4rem);
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
line-height: 1.3;
|
line-height: 1.3;
|
||||||
|
|||||||
@@ -13,33 +13,23 @@
|
|||||||
<label class="block text-sm text-slate-200 mb-1">AI Role Prompt (opcional)</label>
|
<label class="block text-sm text-slate-200 mb-1">AI Role Prompt (opcional)</label>
|
||||||
<textarea name="aiRolePrompt" id="aiRoleInput" rows="4" class="w-full rounded p-2 bg-transparent border border-white/6" placeholder="E.g. Actúa como un moderador amigable..."><%= (guildConfig && guildConfig.aiRolePrompt) || '' %></textarea>
|
<textarea name="aiRolePrompt" id="aiRoleInput" rows="4" class="w-full rounded p-2 bg-transparent border border-white/6" placeholder="E.g. Actúa como un moderador amigable..."><%= (guildConfig && guildConfig.aiRolePrompt) || '' %></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm text-slate-200 mb-1">Roles de staff</label>
|
<label class="block text-sm text-slate-200 mb-1">Roles de staff</label>
|
||||||
|
<% const selectedStaff = (guildConfig && Array.isArray(guildConfig.staff) ? guildConfig.staff.map(String) : (guildConfig && guildConfig.staff ? String(guildConfig.staff).split(',') : [])) || []; %>
|
||||||
<% if (typeof guildRoles !== 'undefined' && guildRoles && guildRoles.length) { %>
|
<% if (typeof guildRoles !== 'undefined' && guildRoles && guildRoles.length) { %>
|
||||||
<div class="mb-2">
|
<div class="mb-2">
|
||||||
<input id="staffFilter" type="search" placeholder="Filtrar roles..." class="w-full rounded p-2 bg-transparent border border-white/6" />
|
<div id="staffTagsContainer" class="w-full rounded p-2 bg-transparent border border-white/6">
|
||||||
|
<div id="staffChips" class="flex flex-wrap gap-2"></div>
|
||||||
|
<input id="staffTagInput" type="text" placeholder="Buscar roles..." autocomplete="off" class="w-full bg-transparent outline-none text-sm p-1" />
|
||||||
</div>
|
</div>
|
||||||
<select id="staffSelect" name="staffSelect" multiple class="w-full rounded p-2 bg-transparent border border-white/6 h-36">
|
<ul id="staffSuggestions" class="mt-1 bg-white/4 rounded max-h-40 overflow-auto hidden z-50"></ul>
|
||||||
<% const selectedStaff = (guildConfig && Array.isArray(guildConfig.staff) ? guildConfig.staff.map(String) : (guildConfig && guildConfig.staff ? String(guildConfig.staff).split(',') : [])) || []; %>
|
<input type="hidden" id="staffHidden" name="staff" value="<%= selectedStaff.join(',') %>" />
|
||||||
<% guildRoles.forEach(r => { %>
|
<div class="text-xs text-slate-300 mt-1">Selecciona roles del servidor. Escribe para filtrar, pulsa Enter para elegir.</div>
|
||||||
<option value="<%= r.id %>" <%= selectedStaff.includes(String(r.id)) ? 'selected' : '' %>><%= r.name %> — <%= r.id %></option>
|
|
||||||
<% }) %>
|
|
||||||
</select>
|
|
||||||
<% } else { %>
|
|
||||||
<% const fallbackStaff = (guildConfig && Array.isArray(guildConfig.staff) ? guildConfig.staff.map(String) : (guildConfig && guildConfig.staff ? String(guildConfig.staff).split(',') : [])) || []; %>
|
|
||||||
<% if (fallbackStaff.length) { %>
|
|
||||||
<div class="mb-2">
|
|
||||||
<input id="staffFilter" type="search" placeholder="Filtrar roles..." class="w-full rounded p-2 bg-transparent border border-white/6" />
|
|
||||||
</div>
|
</div>
|
||||||
<select id="staffSelect" name="staffSelect" multiple class="w-full rounded p-2 bg-transparent border border-white/6 h-36">
|
|
||||||
<% fallbackStaff.forEach(id => { %>
|
|
||||||
<option value="<%= id %>" <%= (Array.isArray(guildConfig.staff) ? guildConfig.staff.map(String).includes(String(id)) : String(guildConfig.staff || '').split(',').map(s=>s.trim()).includes(String(id))) ? 'selected' : '' %>>ID: <%= id %></option>
|
|
||||||
<% }) %>
|
|
||||||
</select>
|
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
<input type="text" name="staff" id="staffInput" value="<%= (guildConfig && (Array.isArray(guildConfig.staff) ? guildConfig.staff.join(',') : guildConfig.staff)) || '' %>" class="w-full rounded p-2 bg-transparent border border-white/6" placeholder="123... , 456..." />
|
<input type="text" name="staff" id="staffInput" value="<%= (guildConfig && (Array.isArray(guildConfig.staff) ? guildConfig.staff.join(',') : guildConfig.staff)) || '' %>" class="w-full rounded p-2 bg-transparent border border-white/6" placeholder="123... , 456..." />
|
||||||
<div class="text-xs text-slate-300 mt-1">No se pudo obtener roles desde la API ni hay roles guardados. Introduce IDs manualmente separadas por coma.</div>
|
<div class="text-xs text-slate-300 mt-1">No se pudo obtener roles desde la API. Introduce IDs manualmente separadas por coma.</div>
|
||||||
<% } %>
|
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
@@ -98,4 +88,134 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
})();
|
})();
|
||||||
|
(function(){
|
||||||
|
const form = document.getElementById('guildSettingsForm');
|
||||||
|
const status = document.getElementById('saveStatus');
|
||||||
|
|
||||||
|
// Multi-select tag/autocomplete for roles
|
||||||
|
const staffTagInput = document.getElementById('staffTagInput');
|
||||||
|
const staffSuggestions = document.getElementById('staffSuggestions');
|
||||||
|
const staffChips = document.getElementById('staffChips');
|
||||||
|
const staffHidden = document.getElementById('staffHidden');
|
||||||
|
|
||||||
|
// Prepare role options (id, name, color) from server-side `guildRoles`
|
||||||
|
<% const ROLE_JSON = JSON.stringify((guildRoles||[]).map(r=>{ var c; if (typeof r.color !== 'undefined' && r.color !== null) { if (typeof r.color === 'number') { c = '#'+('000000'+r.color.toString(16)).slice(-6); } else { c = (''+r.color).replace(/^#?/, '#'); } } else { c = (r.colorHex||r.hex||'#8b95a0'); } return { id: String(r.id), name: r.name, color: c }; })); %>
|
||||||
|
const ROLE_OPTIONS = <%- ROLE_JSON %>;
|
||||||
|
// Initial selected staff IDs (strings)
|
||||||
|
const INITIAL_STAFF = <%- JSON.stringify(selectedStaff || []) %>;
|
||||||
|
|
||||||
|
let selectedIds = Array.isArray(INITIAL_STAFF) ? INITIAL_STAFF.slice() : [];
|
||||||
|
|
||||||
|
function renderChips(){
|
||||||
|
staffChips.innerHTML = '';
|
||||||
|
for(const id of selectedIds){
|
||||||
|
const role = ROLE_OPTIONS.find(r=>r.id===String(id));
|
||||||
|
const label = role ? role.name : String(id);
|
||||||
|
const chip = document.createElement('div');
|
||||||
|
chip.className = 'px-2 py-1 bg-white/6 rounded-full text-sm flex items-center gap-2';
|
||||||
|
const swatch = role && role.color ? `<span class="w-3 h-3 rounded-full inline-block" style="background:${role.color}"></span>` : '';
|
||||||
|
chip.innerHTML = `${swatch}<span class="truncate">${label}</span><button type="button" class="ml-2 text-xs text-slate-300 remove-chip" data-id="${id}">✕</button>`;
|
||||||
|
staffChips.appendChild(chip);
|
||||||
|
}
|
||||||
|
// update hidden input
|
||||||
|
if(staffHidden) staffHidden.value = selectedIds.join(',');
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSuggestions(query){
|
||||||
|
if(!staffSuggestions) return;
|
||||||
|
const q = (query||'').trim().toLowerCase();
|
||||||
|
const filtered = ROLE_OPTIONS.filter(r=> !selectedIds.includes(String(r.id)) && (!q || r.name.toLowerCase().includes(q) || String(r.id).includes(q)) ).slice(0,50);
|
||||||
|
if(!filtered.length){ staffSuggestions.classList.add('hidden'); staffSuggestions.innerHTML=''; return; }
|
||||||
|
staffSuggestions.classList.remove('hidden');
|
||||||
|
staffSuggestions.innerHTML = filtered.map(r=>`<li class="p-2 hover:bg-white/10 cursor-pointer flex items-center gap-2" data-id="${r.id}"><span class="w-3 h-3 rounded-full inline-block" style="background:${r.color}"></span><span class="truncate">${r.name}</span></li>`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
function addId(id){
|
||||||
|
id = String(id);
|
||||||
|
if(!id) return;
|
||||||
|
if(selectedIds.includes(id)) return;
|
||||||
|
selectedIds.push(id);
|
||||||
|
renderChips();
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeId(id){
|
||||||
|
id = String(id);
|
||||||
|
selectedIds = selectedIds.filter(i=>String(i)!==id);
|
||||||
|
renderChips();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(staffTagInput){
|
||||||
|
// initial render
|
||||||
|
renderChips();
|
||||||
|
|
||||||
|
staffTagInput.addEventListener('input', (e)=>{
|
||||||
|
showSuggestions(e.target.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
staffTagInput.addEventListener('keydown', (e)=>{
|
||||||
|
if(e.key === 'Enter'){
|
||||||
|
e.preventDefault();
|
||||||
|
// pick first suggestion if any, otherwise ignore
|
||||||
|
const first = staffSuggestions.querySelector('li');
|
||||||
|
if(first){ addId(first.dataset.id); staffTagInput.value=''; staffSuggestions.classList.add('hidden'); }
|
||||||
|
} else if (e.key === 'Backspace' && !staffTagInput.value){
|
||||||
|
// remove last
|
||||||
|
selectedIds.pop(); renderChips();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('click', (ev)=>{
|
||||||
|
if(!staffSuggestions) return;
|
||||||
|
if(ev.target.closest && ev.target.closest('#staffSuggestions')) return;
|
||||||
|
if(ev.target === staffTagInput) return;
|
||||||
|
staffSuggestions.classList.add('hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
staffSuggestions.addEventListener('click', (ev)=>{
|
||||||
|
const li = ev.target.closest('li');
|
||||||
|
if(!li) return;
|
||||||
|
addId(li.dataset.id);
|
||||||
|
staffTagInput.value='';
|
||||||
|
staffSuggestions.classList.add('hidden');
|
||||||
|
staffTagInput.focus();
|
||||||
|
});
|
||||||
|
|
||||||
|
staffChips.addEventListener('click', (ev)=>{
|
||||||
|
const btn = ev.target.closest('.remove-chip');
|
||||||
|
if(!btn) return;
|
||||||
|
removeId(btn.dataset.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
form.addEventListener('submit', async (e)=>{
|
||||||
|
e.preventDefault();
|
||||||
|
status.textContent = 'Guardando...';
|
||||||
|
const prefix = document.getElementById('prefixInput').value.trim();
|
||||||
|
const aiRolePrompt = document.getElementById('aiRoleInput').value.trim();
|
||||||
|
let staffArr = [];
|
||||||
|
if(typeof selectedIds !== 'undefined' && selectedIds && selectedIds.length){
|
||||||
|
staffArr = selectedIds.slice();
|
||||||
|
} else if (document.getElementById('staffInput')){
|
||||||
|
const staffRaw = document.getElementById('staffInput').value.trim();
|
||||||
|
staffArr = staffRaw ? staffRaw.split(',').map(s=>s.trim()).filter(Boolean) : [];
|
||||||
|
}
|
||||||
|
const payload = { prefix: prefix, aiRolePrompt: aiRolePrompt.length ? aiRolePrompt : null, staff: staffArr };
|
||||||
|
try {
|
||||||
|
const res = await fetch(`/api/dashboard/${encodeURIComponent('<%= selectedGuild %>')}/settings`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
if (res.ok && json.ok) {
|
||||||
|
status.textContent = 'Guardado';
|
||||||
|
setTimeout(()=> status.textContent = '', 2500);
|
||||||
|
} else {
|
||||||
|
status.textContent = json.error || 'Error';
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
status.textContent = 'Error de red';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user