(function () { // 스크립트가 실수로 2번 로드돼도 바인딩 1번만 if (window.__ADMIN_UI_JS_BOUND__) return; window.__ADMIN_UI_JS_BOUND__ = true; // ----------------------- // click handlers // ----------------------- document.addEventListener('click', (e) => { const t = e.target; // password toggle if (t && t.matches('[data-toggle="password"]')) { const pw = document.getElementById('password'); if (!pw) return; const isPw = pw.type === 'password'; pw.type = isPw ? 'text' : 'password'; t.textContent = isPw ? '숨기기' : '보기'; return; } }); // ----------------------- // data-confirm (ONLY ONCE) // - capture 단계에서 먼저 실행되어 // 취소 시 다른 submit 리스너(버튼 disable 등) 실행 안 됨 // ----------------------- document.addEventListener('submit', (e) => { const form = e.target; if (!(form instanceof HTMLFormElement)) return; const raw = form.getAttribute('data-confirm'); if (!raw) return; // 이미 confirm 통과한 submit이면 다시 confirm 금지 if (form.dataset.confirmed === '1') return; // "\n" 문자, " " 엔티티 모두 줄바꿈 처리 const msg = String(raw) .replace(/\\n/g, '\n') .replace(/ /g, '\n'); if (!window.confirm(msg)) { e.preventDefault(); e.stopImmediatePropagation(); return; } form.dataset.confirmed = '1'; // confirm 통과 -> 그대로 진행 (다른 submit 리스너 정상 실행) }, true); // ----------------------- // login submit UI (disable + text) // ----------------------- // document.addEventListener('submit', (e) => { // const form = e.target; // if (!form || !form.matches('[data-form="login"]')) return; // // const btn = form.querySelector('[data-submit]'); // if (btn) { // btn.disabled = true; // btn.dataset.original = btn.textContent; // btn.textContent = '처리 중...'; // } // }); // ----------------------- // showMsg (toast) // ----------------------- if (typeof window.showMsg !== 'function') { function ensureWrap() { let wrap = document.getElementById('a-toast-wrap'); if (!wrap) { wrap = document.createElement('div'); wrap.id = 'a-toast-wrap'; wrap.className = 'a-toast-wrap'; document.body.appendChild(wrap); } return wrap; } window.showMsg = function (message, opt = {}) { const type = opt.type || 'info'; // success | info | warn | danger const title = opt.title || ''; const wrap = ensureWrap(); const toast = document.createElement('div'); toast.className = `a-toast a-toast--${type}`; const t = document.createElement('div'); t.className = 'a-toast__title'; t.textContent = title || (type === 'success' ? '완료' : type === 'danger' ? '오류' : '안내'); const m = document.createElement('div'); m.className = 'a-toast__msg'; m.textContent = String(message || ''); toast.appendChild(t); toast.appendChild(m); wrap.appendChild(toast); const ttl = Number(opt.ttl || 3000); window.setTimeout(() => { toast.style.opacity = '0'; toast.style.transform = 'translateY(-4px)'; toast.style.transition = 'all .15s ease'; window.setTimeout(() => toast.remove(), 180); }, ttl); toast.addEventListener('click', () => toast.remove()); return Promise.resolve(); }; } // ----------------------- // Sidebar accordion // - title click: toggle, others close // - item click: keep its group open, others close // - on load: open group containing .is-active // ----------------------- function __closeAllGroups(navEl, exceptGroup = null) { navEl.querySelectorAll('.a-nav__group.is-open').forEach(g => { if (exceptGroup && g === exceptGroup) return; g.classList.remove('is-open'); const b = g.querySelector('.a-nav__titlebtn'); if (b) b.setAttribute('aria-expanded', 'false'); }); } document.addEventListener('click', (e) => { const btn = e.target.closest('.a-nav__titlebtn[data-nav-toggle]'); if (!btn) return; const group = btn.closest('.a-nav__group'); const nav = btn.closest('.a-nav'); if (!group || !nav) return; const wasOpen = group.classList.contains('is-open'); // accordion: always close others __closeAllGroups(nav); // toggle current if (!wasOpen) { group.classList.add('is-open'); btn.setAttribute('aria-expanded', 'true'); } else { btn.setAttribute('aria-expanded', 'false'); } }); document.addEventListener('click', (e) => { // submenu item click const link = e.target.closest('.a-nav__items a.a-nav__item[href]'); if (!link) return; const group = link.closest('.a-nav__group'); const nav = link.closest('.a-nav'); if (!group || !nav) return; __closeAllGroups(nav, group); group.classList.add('is-open'); const btn = group.querySelector('.a-nav__titlebtn'); if (btn) btn.setAttribute('aria-expanded', 'true'); }); document.addEventListener('DOMContentLoaded', () => { const nav = document.querySelector('.a-nav'); if (!nav) return; // aria sync nav.querySelectorAll('.a-nav__group').forEach(g => { const btn = g.querySelector('.a-nav__titlebtn'); if (btn) btn.setAttribute('aria-expanded', g.classList.contains('is-open') ? 'true' : 'false'); }); // if server already opened (groupActive) keep it if (nav.querySelector('.a-nav__group.is-open')) return; // else open group containing active item const active = nav.querySelector('.a-nav__items .a-nav__item.is-active'); if (!active) return; const group = active.closest('.a-nav__group'); if (!group) return; __closeAllGroups(nav, group); group.classList.add('is-open'); const btn = group.querySelector('.a-nav__titlebtn'); if (btn) btn.setAttribute('aria-expanded', 'true'); }); // flash 자동 표시 document.addEventListener('DOMContentLoaded', async () => { const list = window.__adminFlash; if (!Array.isArray(list) || list.length === 0) return; for (const f of list) { await window.showMsg(f.message, { type: f.type, title: f.title, ttl: 3200 }); } window.__adminFlash = []; }); })();