mirror of
https://github.com/sotam0316/brain_dogfood.git
synced 2026-04-25 03:48:38 +09:00
Initial Global Release v1.0 (Localization & Security Hardening)
This commit is contained in:
+194
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* 뇌사료 메인 엔트리 포인트 (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);
|
||||
});
|
||||
Reference in New Issue
Block a user