Public Source Viewer

비나래아카이브 개발자 포털

실제 서비스 구조를 살펴볼 수 있는 공개용 코드 뷰어입니다. 인증, 세션, 외부 연동, 토큰, 관리자 식별 등 보안상 민감한 구현은 파일 단위 또는 줄 단위로 검열됩니다.

Redacted View
public/js/popup.js
공개 가능
1 /* popup.js — showAlert / showConfirm / showPrompt */
2 (function () {
3 /* ── CSS (한 번만 삽입) ── */
4 const style = document.createElement('style');
5 style.textContent = `
6 .popup-overlay{position:fixed;inset:0;background:rgba(0,0,0,.45);display:flex;align-items:center;justify-content:center;z-index:99999;opacity:0;transition:opacity .15s}
7 .popup-overlay.show{opacity:1}
8 .popup-box{background:var(--bg-secondary,#f2f0eb);color:var(--text-primary,#3e3a36);border:1px solid var(--border-color,#ccc6bc);border-radius:14px;padding:28px 30px 20px;min-width:320px;max-width:420px;width:90%;box-shadow:0 8px 32px rgba(0,0,0,.25);transform:scale(.92);transition:transform .15s}
9 .popup-overlay.show .popup-box{transform:scale(1)}
10 .popup-msg{font-size:.95rem;line-height:1.6;white-space:pre-wrap;word-break:break-word;margin-bottom:20px}
11 .popup-input{width:100%;padding:10px 12px;border:1px solid var(--border-color,#ccc6bc);border-radius:8px;background:var(--bg-main,#e4dfd7);color:var(--text-primary,#3e3a36);font-size:.95rem;margin-bottom:18px;outline:none;box-sizing:border-box}
12 .popup-input:focus{border-color:var(--accent-color,#b45309)}
13 .popup-btns{display:flex;justify-content:flex-end;gap:10px}
14 .popup-btn{padding:8px 22px;border:none;border-radius:8px;font-size:.9rem;font-weight:600;cursor:pointer;font-family:inherit;transition:filter .12s}
15 .popup-btn:hover{filter:brightness(.9)}
16 .popup-btn-cancel{background:var(--bg-tertiary,#dcd6ce);color:var(--text-secondary,#69615c)}
17 .popup-btn-ok{background:var(--accent-color,#b45309);color:#fff}
18 `;
19 document.head.appendChild(style);
20
21 /* ── 유틸 ── */
22 function create(tag, cls, text) {
23 const el = document.createElement(tag);
24 if (cls) el.className = cls;
25 if (text !== undefined) el.textContent = text;
26 return el;
27 }
28
29 function buildOverlay() {
30 const overlay = create('div', 'popup-overlay');
31 const box = create('div', 'popup-box');
32 overlay.appendChild(box);
33 document.body.appendChild(overlay);
34 // trigger reflow for animation
35 overlay.offsetHeight;
36 overlay.classList.add('show');
37 return { overlay, box };
38 }
39
40 function destroy(overlay) {
41 overlay.classList.remove('show');
42 setTimeout(() => overlay.remove(), 160);
43 }
44
45 /* ── showAlert ── */
46 window.showAlert = function (message) {
47 return new Promise(function (resolve) {
48 var ref = buildOverlay();
49 var overlay = ref.overlay;
50 var box = ref.box;
51
52 var msg = create('div', 'popup-msg', message);
53 var btns = create('div', 'popup-btns');
54 var ok = create('button', 'popup-btn popup-btn-ok', '확인');
55 btns.appendChild(ok);
56 box.appendChild(msg);
57 box.appendChild(btns);
58
59 function close() { destroy(overlay); resolve(); }
60 ok.addEventListener('click', close);
61 overlay.addEventListener('click', function (e) { if (e.target === overlay) close(); });
62 document.addEventListener('keydown', function handler(e) {
63 if (e.key === 'Enter' || e.key === 'Escape') { document.removeEventListener('keydown', handler); close(); }
64 });
65 ok.focus();
66 });
67 };
68
69 /* ── showConfirm ── */
70 window.showConfirm = function (message) {
71 return new Promise(function (resolve) {
72 var ref = buildOverlay();
73 var overlay = ref.overlay;
74 var box = ref.box;
75
76 var msg = create('div', 'popup-msg', message);
77 var btns = create('div', 'popup-btns');
78 var cancel = create('button', 'popup-btn popup-btn-cancel', '취소');
79 var ok = create('button', 'popup-btn popup-btn-ok', '확인');
80 btns.appendChild(cancel);
81 btns.appendChild(ok);
82 box.appendChild(msg);
83 box.appendChild(btns);
84
85 var done = false;
86 function finish(val) { if (done) return; done = true; destroy(overlay); resolve(val); }
87 ok.addEventListener('click', function () { finish(true); });
88 cancel.addEventListener('click', function () { finish(false); });
89 overlay.addEventListener('click', function (e) { if (e.target === overlay) finish(false); });
90 document.addEventListener('keydown', function handler(e) {
91 if (e.key === 'Enter') { document.removeEventListener('keydown', handler); finish(true); }
92 if (e.key === 'Escape') { document.removeEventListener('keydown', handler); finish(false); }
93 });
94 ok.focus();
95 });
96 };
97
98 /* ── showPrompt ── */
99 window.showPrompt = function (message, defaultValue) {
100 return new Promise(function (resolve) {
101 var ref = buildOverlay();
102 var overlay = ref.overlay;
103 var box = ref.box;
104
105 var msg = create('div', 'popup-msg', message);
106 var input = create('input', 'popup-input');
107 input.type = 'text';
108 if (defaultValue !== undefined) input.value = defaultValue;
109 var btns = create('div', 'popup-btns');
110 var cancel = create('button', 'popup-btn popup-btn-cancel', '취소');
111 var ok = create('button', 'popup-btn popup-btn-ok', '확인');
112 btns.appendChild(cancel);
113 btns.appendChild(ok);
114 box.appendChild(msg);
115 box.appendChild(input);
116 box.appendChild(btns);
117
118 var done = false;
119 function finish(val) { if (done) return; done = true; destroy(overlay); resolve(val); }
120 ok.addEventListener('click', function () { finish(input.value); });
121 cancel.addEventListener('click', function () { finish(null); });
122 overlay.addEventListener('click', function (e) { if (e.target === overlay) finish(null); });
123 input.addEventListener('keydown', function (e) {
124 if (e.key === 'Enter') { e.preventDefault(); finish(input.value); }
125 if (e.key === 'Escape') { e.preventDefault(); finish(null); }
126 });
127 input.focus();
128 });
129 };
130
131 /* ── onsubmit="return confirm(...)" 대체용 자동 바인딩 ── */
132 document.addEventListener('DOMContentLoaded', function () {
133 document.querySelectorAll('form[data-confirm]').forEach(function (form) {
134 form.addEventListener('submit', function (e) {
135 e.preventDefault();
136 showConfirm(form.dataset.confirm).then(function (ok) {
137 if (ok) {
138 // 임시로 data-confirm 제거 후 submit (무한 루프 방지)
139 var msg = form.dataset.confirm;
140 delete form.dataset.confirm;
141 form.submit();
142 form.dataset.confirm = msg;
143 }
144 });
145 });
146 });
147 });
148 })();
149