Files
document-server/frontend/static/js/notebooks.js
Hyungi Ahn 3e0a03f149 🐛 Fix Alpine.js SyntaxError and backlink visibility issues
- 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
2025-08-26 23:50:48 +09:00

278 lines
8.1 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 노트북 관리 애플리케이션 컴포넌트
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}&notebook_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);
}
}
});