Public Source Viewer
비나래아카이브 개발자 포털
실제 서비스 구조를 살펴볼 수 있는 공개용 코드 뷰어입니다. 인증, 세션, 외부 연동, 토큰, 관리자 식별 등 보안상 민감한 구현은 파일 단위 또는 줄 단위로 검열됩니다.
public/js/site-app.js
공개 가능
1
(() => {
2
const toggle = document.getElementById('ui-menu-toggle');
3
const sidebar = document.getElementById('ui-sidebar');
4
const dim = document.getElementById('ui-sidebar-dim');
5
if (!toggle || !sidebar || !dim) return;
6
const mobile = window.matchMedia('(max-width: 760px)');
7
const focusable = () => Array.from(sidebar.querySelectorAll('a[href], button:not([disabled])'))
8
.filter(element => element.getClientRects().length);
9
function setMenu(open) {
10
const active = open && mobile.matches;
11
const returnFocus = !active && sidebar.contains(document.activeElement);
12
document.body.classList.toggle('ui-menu-open', active);
13
toggle.setAttribute('aria-expanded', String(active));
14
toggle.setAttribute('aria-label', active ? '메뉴 닫기' : '메뉴 열기');
15
dim.hidden = !active;
16
if (active) (sidebar.querySelector('[aria-current="page"]') || focusable()[0])?.focus();
17
else if (returnFocus && mobile.matches) toggle.focus();
18
}
19
toggle.addEventListener('click', () => setMenu(toggle.getAttribute('aria-expanded') !== 'true'));
20
dim.addEventListener('click', () => setMenu(false));
21
mobile.addEventListener('change', () => setMenu(false));
22
document.addEventListener('keydown', event => {
23
if (event.key === 'Escape') {
24
setMenu(false);
25
document.querySelectorAll('.ui-account-menu[open]').forEach(menu => {
26
menu.open = false;
27
menu.querySelector('summary')?.focus();
28
});
29
}
30
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === 'k') {
31
const search = document.getElementById('ui-global-search');
32
if (search) { event.preventDefault(); setMenu(false); search.focus(); }
33
}
34
if (event.key === 'Tab' && document.body.classList.contains('ui-menu-open')) {
35
const elements = focusable();
36
const first = elements[0], last = elements[elements.length - 1];
37
if (event.shiftKey && document.activeElement === first) { event.preventDefault(); last?.focus(); }
38
else if (!event.shiftKey && document.activeElement === last) { event.preventDefault(); first?.focus(); }
39
}
40
});
41
document.addEventListener('click', event => {
42
document.querySelectorAll('.ui-account-menu[open]').forEach(menu => {
43
if (event.target instanceof Node && !menu.contains(event.target)) menu.open = false;
44
});
45
});
46
})();
47