Files
brain_dogfood/static/app.js
T

195 lines
7.8 KiB
JavaScript

/**
* 뇌사료 메인 엔트리 포인트 (v5.0 리팩토링 완료)
*/
import { API } from './js/api.js';
import { UI } from './js/ui.js';
import { AppService } from './js/AppService.js';
import { EditorManager } from './js/editor.js';
import { ComposerManager } from './js/components/ComposerManager.js';
import { CalendarManager } from './js/components/CalendarManager.js';
import { Visualizer } from './js/components/Visualizer.js';
import { HeatmapManager } from './js/components/HeatmapManager.js';
import { DrawerManager } from './js/components/DrawerManager.js';
import { ModalManager } from './js/components/ModalManager.js';
import { I18nManager } from './js/utils/I18nManager.js';
import { Constants } from './js/utils/Constants.js';
document.addEventListener('DOMContentLoaded', async () => {
// --- 🔹 Initialization ---
await UI.initSettings(); // ⭐ i18n 및 테마 로딩 완료까지 최우선 대기
EditorManager.init('#editor');
// 작성기 초기화 (저장 성공 시 데이터 새로고침 콜백 등록)
ComposerManager.init(() => AppService.refreshData(updateSidebarCallback));
HeatmapManager.init('heatmapContainer'); // 히트맵 초기화
DrawerManager.init();
Visualizer.init('graphContainer');
UI.initSidebarToggle();
// --- 🔹 Callbacks ---
const updateSidebarCallback = (memos, activeGroup) => {
UI.updateSidebar(memos, activeGroup, (newFilter) => {
if (newFilter === Constants.GROUPS.FILES) {
ModalManager.openAssetLibrary((id, ms) => UI.openMemoModal(id, ms));
} else {
AppService.setFilter({ group: newFilter }, updateSidebarCallback);
}
});
};
// 달력 초기화
CalendarManager.init('calendarContainer', (date) => {
AppService.setFilter({ date }, updateSidebarCallback);
});
// 무한 스크롤 초기화
UI.initInfiniteScroll(() => {
AppService.loadMore(updateSidebarCallback);
});
// 드래그 앤 드롭 파일 탐지
EditorManager.bindDropEvent('.composer-wrapper', (shouldOpen) => {
if (shouldOpen && ComposerManager.DOM.composer.style.display === 'none') {
ComposerManager.openEmpty();
}
});
// --- 🔹 Global Event Handlers for Memo Cards ---
window.memoEventHandlers = {
onEdit: (id) => {
const memo = AppService.state.memosCache.find(m => m.id == id);
ComposerManager.openForEdit(memo);
},
onDelete: async (id) => {
if (confirm(I18nManager.t('msg_delete_confirm'))) {
await API.deleteMemo(id);
AppService.refreshData(updateSidebarCallback);
}
},
onAI: async (id) => {
UI.showLoading(true);
try {
await API.triggerAI(id);
await AppService.refreshData(updateSidebarCallback);
} catch (err) { alert(err.message); }
finally { UI.showLoading(false); }
},
onTogglePin: async (id) => {
const memo = AppService.state.memosCache.find(m => m.id == id);
await API.saveMemo({ is_pinned: !memo.is_pinned }, id);
AppService.refreshData(updateSidebarCallback);
},
onToggleStatus: async (id) => {
const memo = AppService.state.memosCache.find(m => m.id == id);
const newStatus = memo.status === 'done' ? 'active' : 'done';
await API.saveMemo({ status: newStatus }, id);
AppService.refreshData(updateSidebarCallback);
},
onOpenModal: (id) => UI.openMemoModal(id, AppService.state.memosCache),
onUnlock: async (id) => {
const password = prompt(I18nManager.t('prompt_password'));
if (!password) return;
try {
const data = await API.decryptMemo(id, password);
const memo = AppService.state.memosCache.find(m => m.id == id);
if (memo) {
memo.content = data.content;
memo.is_encrypted = false;
memo.was_encrypted = true;
memo.tempPassword = password;
// 검색 필터 적용 (현재 데이터 기준)
UI.renderMemos(AppService.state.memosCache, {}, window.memoEventHandlers, false);
}
} catch (err) { alert(err.message); }
}
};
// --- 🔹 Search & Graph ---
const searchInput = document.getElementById('searchInput');
let searchTimer;
searchInput.oninput = () => {
clearTimeout(searchTimer);
searchTimer = setTimeout(() => {
AppService.setFilter({ query: searchInput.value }, updateSidebarCallback);
}, 300);
};
document.getElementById('openGraphBtn').onclick = () => {
document.getElementById('graphModal').classList.add('active');
setTimeout(() => {
Visualizer.render(AppService.state.memosCache, (id) => {
document.getElementById('graphModal').classList.remove('active');
UI.openMemoModal(id, AppService.state.memosCache);
});
}, 150);
};
document.getElementById('closeGraphBtn').onclick = () => {
document.getElementById('graphModal').classList.remove('active');
};
document.getElementById('openExplorerBtn').onclick = () => {
DrawerManager.open(AppService.state.memosCache, AppService.state.currentFilterGroup, (filter) => {
AppService.setFilter({ group: filter }, updateSidebarCallback);
});
};
// --- 🔹 Global Shortcuts (Comprehensive Shift to Ctrl-based System) ---
document.addEventListener('keydown', (e) => {
const isCtrl = e.ctrlKey || e.metaKey;
const isAlt = e.altKey;
const key = e.key.toLowerCase();
// 1. ESC: 모든 창 닫기
if (e.key === 'Escape') {
document.querySelectorAll('.modal.active, .drawer.active').forEach(el => el.classList.remove('active'));
if (ComposerManager.DOM.composer.style.display === 'block') ComposerManager.close();
return;
}
// 2. Ctrl + Enter / Ctrl + S: 저장 (작성기 열려있을 때)
if (isCtrl && (key === 'enter' || key === 's')) {
if (ComposerManager.DOM.composer.style.display === 'block') {
e.preventDefault();
ComposerManager.handleSave(updateSidebarCallback);
}
return;
}
// 3. Ctrl + Shift + Key 조합들 (네비게이션)
if (isCtrl && e.shiftKey) {
e.preventDefault();
switch (key) {
case 'n': // 새 메모
ComposerManager.openEmpty();
break;
case 'g': // 지식 네뷸라
document.getElementById('openGraphBtn').click();
break;
case 'e': // 지식 탐색기
document.getElementById('openExplorerBtn').click();
break;
case 'c': // 캘린더 토글
CalendarManager.isCollapsed = !CalendarManager.isCollapsed;
localStorage.setItem('calendar_collapsed', CalendarManager.isCollapsed);
CalendarManager.updateCollapseUI();
break;
case 'q': // 닫기
document.querySelectorAll('.modal.active, .drawer.active').forEach(el => el.classList.remove('active'));
ComposerManager.close();
break;
}
}
// 4. Quake-style Shortcut: Alt + ` (새 메모)
if (isAlt && key === '`') {
e.preventDefault();
ComposerManager.openEmpty();
}
});
// --- 🔹 App Start ---
AppService.refreshData(updateSidebarCallback);
});