(function () { 'use strict'; let guildData = null; let guildDataPromise = null; async function fetchGuildData() { if (guildData) return guildData; if (guildDataPromise) return guildDataPromise; guildDataPromise = fetch('/api/discord/guild') .then(r => r.json()) .then(data => { guildData = data; return data; }) .catch(() => ({ channels: [], roles: [], members: [], categories: [] })); return guildDataPromise; } async function renderChannelSelect(el, currentValue, filter) { const data = await fetchGuildData(); const channels = filter ? data.channels.filter(filter) : data.channels; renderSmartSelect(el, channels.map(c => ({ id: c.id, label: `#${c.name}`, sub: c.parentId ? (data.categories.find(cat => cat.id === c.parentId)?.name || null) : null })), currentValue); } async function renderCategorySelect(el, currentValue) { const data = await fetchGuildData(); renderSmartSelect(el, data.categories.map(c => ({ id: c.id, label: c.name })), currentValue); } async function renderRoleSelect(el, currentValue) { const data = await fetchGuildData(); renderSmartSelect(el, data.roles.map(r => ({ id: r.id, label: `@${r.name}`, color: r.color })), currentValue); } async function renderMemberSelect(el, currentValue) { const data = await fetchGuildData(); renderSmartSelect(el, data.members.map(m => ({ id: m.id, label: m.displayName, sub: `@${m.username}`, avatar: m.avatar })), currentValue); } async function renderMultiMemberSelect(el, currentValue) { const data = await fetchGuildData(); const currentIds = (currentValue || '').split(',').map(s => s.trim()).filter(Boolean); renderMultiSelect(el, data.members.map(m => ({ id: m.id, label: m.displayName, sub: `@${m.username}`, avatar: m.avatar })), currentIds); } function buildOptionRow(opt, { selected = false } = {}) { const item = document.createElement('div'); item.className = 'ss-option' + (selected ? ' selected' : ''); if (opt.avatar) { const img = document.createElement('img'); img.className = 'ss-avatar'; img.src = opt.avatar; img.alt = ''; item.appendChild(img); } if (opt.color && opt.color !== '#000000') { const dot = document.createElement('span'); dot.className = 'ss-dot'; dot.style.background = opt.color; item.appendChild(dot); } const label = document.createElement('span'); label.className = 'ss-label'; label.textContent = opt.label; item.appendChild(label); if (opt.sub) { const sub = document.createElement('span'); sub.className = 'ss-sub'; sub.textContent = opt.sub; item.appendChild(sub); } return item; } function setDisplayValue(displayEl, opt) { displayEl.replaceChildren(); const labelSpan = document.createElement('span'); labelSpan.className = 'ss-label'; labelSpan.textContent = opt.label; const idSpan = document.createElement('span'); idSpan.className = 'ss-id'; idSpan.textContent = opt.id; displayEl.appendChild(labelSpan); displayEl.appendChild(idSpan); } function setDisplayPlaceholder(displayEl, text) { displayEl.replaceChildren(); const placeholder = document.createElement('span'); placeholder.className = 'ss-placeholder'; placeholder.textContent = text; displayEl.appendChild(placeholder); } function createDropdown(options, opts) { const { multi = false, getCurrentId = () => null, isExcluded = () => false, onChoose, onClear } = opts; const dropdown = document.createElement('div'); dropdown.className = 'smart-select-dropdown hidden'; const search = document.createElement('input'); search.type = 'text'; search.placeholder = 'Search...'; search.className = 'ss-search'; const list = document.createElement('div'); list.className = 'ss-list'; function renderOptions(filter = '') { list.replaceChildren(); if (!multi && onClear) { const clearOpt = document.createElement('div'); clearOpt.className = 'ss-option ss-clear'; clearOpt.textContent = 'Clear (not set)'; clearOpt.addEventListener('click', onClear); list.appendChild(clearOpt); } const lq = filter.toLowerCase(); const filtered = options.filter(o => { if (isExcluded(o.id)) return false; if (!filter) return true; if (multi) { return o.label.toLowerCase().includes(lq) || o.id.includes(filter); } return o.label.toLowerCase().includes(lq) || (o.sub || '').toLowerCase().includes(lq) || o.id.includes(filter); }); const currentId = getCurrentId(); for (const opt of filtered.slice(0, 50)) { const item = buildOptionRow(opt, { selected: !multi && opt.id === currentId }); item.addEventListener('click', () => onChoose(opt)); list.appendChild(item); } } search.addEventListener('input', () => renderOptions(search.value)); dropdown.appendChild(search); dropdown.appendChild(list); renderOptions(); return { dropdown, search, list, renderOptions }; } function renderSmartSelect(inputEl, options, currentValue) { const wrapper = document.createElement('div'); wrapper.className = 'smart-select'; const display = document.createElement('div'); display.className = 'smart-select-display'; const current = options.find(o => o.id === currentValue); if (current) setDisplayValue(display, current); else setDisplayPlaceholder(display, 'Not set'); const { dropdown, search } = createDropdown(options, { multi: false, getCurrentId: () => inputEl.value, onChoose: (opt) => { inputEl.value = opt.id; setDisplayValue(display, opt); dropdown.classList.add('hidden'); inputEl.dispatchEvent(new Event('change')); }, onClear: () => { inputEl.value = ''; setDisplayPlaceholder(display, 'Not set'); dropdown.classList.add('hidden'); inputEl.dispatchEvent(new Event('change')); } }); display.addEventListener('click', () => { dropdown.classList.toggle('hidden'); if (!dropdown.classList.contains('hidden')) search.focus(); }); document.addEventListener('click', (e) => { if (!wrapper.contains(e.target)) dropdown.classList.add('hidden'); }); wrapper.appendChild(display); wrapper.appendChild(dropdown); inputEl.style.display = 'none'; inputEl.parentNode.insertBefore(wrapper, inputEl.nextSibling); } function renderMultiSelect(inputEl, options, currentIds) { const wrapper = document.createElement('div'); wrapper.className = 'smart-select'; const selected = new Set(currentIds); function updateInput() { inputEl.value = [...selected].join(','); inputEl.dispatchEvent(new Event('change')); } const chipsEl = document.createElement('div'); chipsEl.className = 'ss-chips'; function renderChips() { chipsEl.replaceChildren(); for (const id of selected) { const opt = options.find(o => o.id === id); const chip = document.createElement('span'); chip.className = 'ss-option ss-chip selected'; chip.textContent = opt ? opt.label : id; chip.title = 'Click to remove'; chip.addEventListener('click', () => { selected.delete(id); renderChips(); updateInput(); }); chipsEl.appendChild(chip); } } const addBtn = document.createElement('div'); addBtn.className = 'smart-select-display'; setDisplayPlaceholder(addBtn, '+ Add'); const { dropdown, search, renderOptions } = createDropdown(options, { multi: true, isExcluded: (id) => selected.has(id), onChoose: (opt) => { selected.add(opt.id); renderChips(); renderOptions(search.value); updateInput(); } }); renderChips(); addBtn.addEventListener('click', () => { dropdown.classList.toggle('hidden'); if (!dropdown.classList.contains('hidden')) search.focus(); }); document.addEventListener('click', (e) => { if (!wrapper.contains(e.target)) dropdown.classList.add('hidden'); }); wrapper.appendChild(chipsEl); wrapper.appendChild(addBtn); wrapper.appendChild(dropdown); inputEl.style.display = 'none'; inputEl.parentNode.insertBefore(wrapper, inputEl.nextSibling); } window.DiscordFields = { fetchGuildData, renderChannelSelect, renderCategorySelect, renderRoleSelect, renderMemberSelect, renderMultiMemberSelect }; })();