- Fix SyntaxError in viewer.js line 2868 (.bind(this) issue in setTimeout) - Resolve Alpine.js 'Can't find variable' errors (documentViewer, goBack, etc.) - Fix backlink rendering and persistence during temporary highlights - Add backlink protection and restoration mechanism in highlightAndScrollToText - Implement Note Management System with hierarchical notebooks - Add note highlights and memos functionality - Update cache version to force browser refresh (v=2025012641) - Add comprehensive logging for debugging backlink issues
278 lines
8.1 KiB
JavaScript
278 lines
8.1 KiB
JavaScript
// 노트북 관리 애플리케이션 컴포넌트
|
||
window.notebooksApp = () => ({
|
||
// 상태 관리
|
||
notebooks: [],
|
||
stats: null,
|
||
loading: false,
|
||
saving: false,
|
||
error: '',
|
||
|
||
// 필터링
|
||
searchQuery: '',
|
||
activeOnly: true,
|
||
sortBy: 'updated_at',
|
||
|
||
// 검색 디바운스
|
||
searchTimeout: null,
|
||
|
||
// 모달 상태
|
||
showCreateModal: false,
|
||
showEditModal: false,
|
||
editingNotebook: null,
|
||
|
||
// 노트북 폼
|
||
notebookForm: {
|
||
title: '',
|
||
description: '',
|
||
color: '#3B82F6',
|
||
icon: 'book',
|
||
is_active: true,
|
||
sort_order: 0
|
||
},
|
||
|
||
// 인증 상태
|
||
isAuthenticated: false,
|
||
currentUser: null,
|
||
|
||
// API 클라이언트
|
||
api: null,
|
||
|
||
// 색상 옵션
|
||
availableColors: [
|
||
'#3B82F6', // blue
|
||
'#10B981', // emerald
|
||
'#F59E0B', // amber
|
||
'#EF4444', // red
|
||
'#8B5CF6', // violet
|
||
'#06B6D4', // cyan
|
||
'#84CC16', // lime
|
||
'#F97316', // orange
|
||
'#EC4899', // pink
|
||
'#6B7280' // gray
|
||
],
|
||
|
||
// 아이콘 옵션
|
||
availableIcons: [
|
||
{ value: 'book', label: '📖 책' },
|
||
{ value: 'sticky-note', label: '📝 노트' },
|
||
{ value: 'lightbulb', label: '💡 아이디어' },
|
||
{ value: 'graduation-cap', label: '🎓 학습' },
|
||
{ value: 'briefcase', label: '💼 업무' },
|
||
{ value: 'heart', label: '❤️ 개인' },
|
||
{ value: 'code', label: '💻 개발' },
|
||
{ value: 'palette', label: '🎨 창작' },
|
||
{ value: 'flask', label: '🧪 연구' },
|
||
{ value: 'star', label: '⭐ 즐겨찾기' }
|
||
],
|
||
|
||
// 초기화
|
||
async init() {
|
||
console.log('📚 Notebooks App 초기화 시작');
|
||
|
||
// API 클라이언트 초기화
|
||
this.api = new DocumentServerAPI();
|
||
|
||
// 인증 상태 확인
|
||
await this.checkAuthStatus();
|
||
|
||
if (this.isAuthenticated) {
|
||
await this.loadStats();
|
||
await this.loadNotebooks();
|
||
}
|
||
|
||
// 헤더 로드
|
||
await this.loadHeader();
|
||
},
|
||
|
||
// 인증 상태 확인
|
||
async checkAuthStatus() {
|
||
try {
|
||
const user = await this.api.getCurrentUser();
|
||
this.isAuthenticated = true;
|
||
this.currentUser = user;
|
||
console.log('✅ 인증됨:', user.username || user.email);
|
||
} catch (error) {
|
||
console.log('❌ 인증되지 않음');
|
||
this.isAuthenticated = false;
|
||
this.currentUser = null;
|
||
window.location.href = '/';
|
||
}
|
||
},
|
||
|
||
// 헤더 로드
|
||
async loadHeader() {
|
||
try {
|
||
if (typeof loadHeaderComponent === 'function') {
|
||
await loadHeaderComponent();
|
||
} else if (typeof window.loadHeaderComponent === 'function') {
|
||
await window.loadHeaderComponent();
|
||
} else {
|
||
console.warn('헤더 로더 함수를 찾을 수 없습니다.');
|
||
}
|
||
} catch (error) {
|
||
console.error('헤더 로드 실패:', error);
|
||
}
|
||
},
|
||
|
||
// 통계 정보 로드
|
||
async loadStats() {
|
||
try {
|
||
this.stats = await this.api.getNotebookStats();
|
||
console.log('📊 노트북 통계 로드됨:', this.stats);
|
||
} catch (error) {
|
||
console.error('통계 로드 실패:', error);
|
||
}
|
||
},
|
||
|
||
// 노트북 목록 로드
|
||
async loadNotebooks() {
|
||
this.loading = true;
|
||
this.error = '';
|
||
|
||
try {
|
||
const queryParams = {
|
||
active_only: this.activeOnly,
|
||
sort_by: this.sortBy,
|
||
order: 'desc'
|
||
};
|
||
|
||
if (this.searchQuery) {
|
||
queryParams.search = this.searchQuery;
|
||
}
|
||
|
||
this.notebooks = await this.api.getNotebooks(queryParams);
|
||
console.log('📚 노트북 로드됨:', this.notebooks.length, '개');
|
||
} catch (error) {
|
||
console.error('노트북 로드 실패:', error);
|
||
this.error = '노트북을 불러오는 중 오류가 발생했습니다.';
|
||
} finally {
|
||
this.loading = false;
|
||
}
|
||
},
|
||
|
||
// 검색 디바운스
|
||
debounceSearch() {
|
||
clearTimeout(this.searchTimeout);
|
||
this.searchTimeout = setTimeout(() => {
|
||
this.loadNotebooks();
|
||
}, 300);
|
||
},
|
||
|
||
// 새로고침
|
||
async refreshNotebooks() {
|
||
await Promise.all([
|
||
this.loadStats(),
|
||
this.loadNotebooks()
|
||
]);
|
||
},
|
||
|
||
// 노트북 열기 (노트 목록으로 이동)
|
||
openNotebook(notebook) {
|
||
window.location.href = `/notes.html?notebook_id=${notebook.id}¬ebook_name=${encodeURIComponent(notebook.name)}`;
|
||
},
|
||
|
||
// 노트북 편집
|
||
editNotebook(notebook) {
|
||
this.editingNotebook = notebook;
|
||
this.notebookForm = {
|
||
title: notebook.title,
|
||
description: notebook.description || '',
|
||
color: notebook.color,
|
||
icon: notebook.icon,
|
||
is_active: notebook.is_active,
|
||
sort_order: notebook.sort_order
|
||
};
|
||
this.showEditModal = true;
|
||
},
|
||
|
||
// 노트북 삭제
|
||
async deleteNotebook(notebook) {
|
||
if (!confirm(`"${notebook.title}" 노트북을 삭제하시겠습니까?\n\n${notebook.note_count > 0 ? `포함된 ${notebook.note_count}개의 노트는 미분류 상태가 됩니다.` : ''}`)) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
await this.api.deleteNotebook(notebook.id, true); // force=true
|
||
this.showNotification('노트북이 삭제되었습니다.', 'success');
|
||
await this.refreshNotebooks();
|
||
} catch (error) {
|
||
console.error('노트북 삭제 실패:', error);
|
||
this.showNotification('노트북 삭제에 실패했습니다.', 'error');
|
||
}
|
||
},
|
||
|
||
// 노트북 저장
|
||
async saveNotebook() {
|
||
if (!this.notebookForm.title.trim()) {
|
||
this.showNotification('제목을 입력해주세요.', 'error');
|
||
return;
|
||
}
|
||
|
||
this.saving = true;
|
||
|
||
try {
|
||
if (this.showEditModal && this.editingNotebook) {
|
||
// 편집
|
||
await this.api.updateNotebook(this.editingNotebook.id, this.notebookForm);
|
||
this.showNotification('노트북이 수정되었습니다.', 'success');
|
||
} else {
|
||
// 생성
|
||
await this.api.createNotebook(this.notebookForm);
|
||
this.showNotification('노트북이 생성되었습니다.', 'success');
|
||
}
|
||
|
||
this.closeModal();
|
||
await this.refreshNotebooks();
|
||
} catch (error) {
|
||
console.error('노트북 저장 실패:', error);
|
||
this.showNotification('노트북 저장에 실패했습니다.', 'error');
|
||
} finally {
|
||
this.saving = false;
|
||
}
|
||
},
|
||
|
||
// 모달 닫기
|
||
closeModal() {
|
||
this.showCreateModal = false;
|
||
this.showEditModal = false;
|
||
this.editingNotebook = null;
|
||
this.notebookForm = {
|
||
title: '',
|
||
description: '',
|
||
color: '#3B82F6',
|
||
icon: 'book',
|
||
is_active: true,
|
||
sort_order: 0
|
||
};
|
||
},
|
||
|
||
// 날짜 포맷팅
|
||
formatDate(dateString) {
|
||
const date = new Date(dateString);
|
||
const now = new Date();
|
||
const diffTime = Math.abs(now - date);
|
||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||
|
||
if (diffDays === 1) {
|
||
return '오늘';
|
||
} else if (diffDays === 2) {
|
||
return '어제';
|
||
} else if (diffDays <= 7) {
|
||
return `${diffDays - 1}일 전`;
|
||
} else {
|
||
return date.toLocaleDateString('ko-KR');
|
||
}
|
||
},
|
||
|
||
// 알림 표시
|
||
showNotification(message, type = 'info') {
|
||
if (type === 'error') {
|
||
alert('❌ ' + message);
|
||
} else if (type === 'success') {
|
||
alert('✅ ' + message);
|
||
} else {
|
||
alert('ℹ️ ' + message);
|
||
}
|
||
}
|
||
});
|