본문으로 건너뛰기
Public Source Viewer

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

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

Redacted View
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