- 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
405 lines
13 KiB
JavaScript
405 lines
13 KiB
JavaScript
// 노트 관리 애플리케이션 컴포넌트
|
|
window.notesApp = () => ({
|
|
// 상태 관리
|
|
notes: [],
|
|
stats: null,
|
|
loading: false,
|
|
error: '',
|
|
|
|
// 필터링
|
|
searchQuery: '',
|
|
selectedType: '',
|
|
publishedOnly: false,
|
|
selectedNotebook: '',
|
|
|
|
// 노트북 관련
|
|
availableNotebooks: [],
|
|
|
|
// 일괄 선택 관련
|
|
selectedNotes: [],
|
|
bulkNotebookId: '',
|
|
|
|
// 노트북 생성 관련
|
|
showCreateNotebookModal: false,
|
|
creatingNotebook: false,
|
|
newNotebookForm: {
|
|
name: '',
|
|
description: '',
|
|
color: '#3B82F6',
|
|
icon: 'book'
|
|
},
|
|
|
|
// 색상 및 아이콘 옵션
|
|
availableColors: [
|
|
'#3B82F6', '#10B981', '#F59E0B', '#EF4444', '#8B5CF6',
|
|
'#06B6D4', '#84CC16', '#F97316', '#EC4899', '#6B7280'
|
|
],
|
|
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: '⭐ 즐겨찾기' }
|
|
],
|
|
|
|
// 검색 디바운스
|
|
searchTimeout: null,
|
|
|
|
// 인증 상태
|
|
isAuthenticated: false,
|
|
currentUser: null,
|
|
|
|
// API 클라이언트
|
|
api: null,
|
|
|
|
// 초기화
|
|
async init() {
|
|
console.log('🚀 Notes App 초기화 시작');
|
|
|
|
// API 클라이언트 초기화
|
|
this.api = new DocumentServerAPI();
|
|
console.log('🔧 API 클라이언트 초기화됨:', this.api);
|
|
console.log('🔧 getNotebooks 메서드 존재 여부:', typeof this.api.getNotebooks);
|
|
|
|
// URL 파라미터 확인 (노트북 필터)
|
|
const urlParams = new URLSearchParams(window.location.search);
|
|
const notebookId = urlParams.get('notebook_id');
|
|
const notebookName = urlParams.get('notebook_name');
|
|
|
|
if (notebookId) {
|
|
this.selectedNotebook = notebookId;
|
|
console.log('🔍 노트북 필터 적용:', notebookName || notebookId);
|
|
}
|
|
|
|
// 인증 상태 확인
|
|
await this.checkAuthStatus();
|
|
|
|
if (this.isAuthenticated) {
|
|
await this.loadNotebooks();
|
|
await this.loadStats();
|
|
await this.loadNotes();
|
|
}
|
|
|
|
// 헤더 로드
|
|
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 {
|
|
await window.headerLoader.loadHeader();
|
|
} catch (error) {
|
|
console.error('헤더 로드 실패:', error);
|
|
}
|
|
},
|
|
|
|
// 노트북 목록 로드
|
|
async loadNotebooks() {
|
|
try {
|
|
console.log('📚 노트북 로드 시작...');
|
|
console.log('🔧 API 메서드 확인:', typeof this.api.getNotebooks);
|
|
|
|
if (typeof this.api.getNotebooks !== 'function') {
|
|
throw new Error('getNotebooks 메서드가 존재하지 않습니다');
|
|
}
|
|
|
|
// 임시: 직접 API 호출
|
|
this.availableNotebooks = await this.api.get('/notebooks/', { active_only: true });
|
|
console.log('📚 노트북 로드됨:', this.availableNotebooks.length, '개');
|
|
} catch (error) {
|
|
console.error('노트북 로드 실패:', error);
|
|
this.availableNotebooks = [];
|
|
}
|
|
},
|
|
|
|
// 통계 정보 로드
|
|
async loadStats() {
|
|
try {
|
|
this.stats = await this.api.get('/note-documents/stats');
|
|
console.log('📊 통계 로드됨:', this.stats);
|
|
} catch (error) {
|
|
console.error('통계 로드 실패:', error);
|
|
}
|
|
},
|
|
|
|
// 노트 목록 로드
|
|
async loadNotes() {
|
|
this.loading = true;
|
|
this.error = '';
|
|
|
|
try {
|
|
const queryParams = {};
|
|
|
|
if (this.searchQuery) {
|
|
queryParams.search = this.searchQuery;
|
|
}
|
|
if (this.selectedType) {
|
|
queryParams.note_type = this.selectedType;
|
|
}
|
|
if (this.publishedOnly) {
|
|
queryParams.published_only = 'true';
|
|
}
|
|
if (this.selectedNotebook) {
|
|
if (this.selectedNotebook === 'unassigned') {
|
|
queryParams.notebook_id = 'null';
|
|
} else {
|
|
queryParams.notebook_id = this.selectedNotebook;
|
|
}
|
|
}
|
|
|
|
this.notes = await this.api.getNoteDocuments(queryParams);
|
|
console.log('📝 노트 로드됨:', this.notes.length, '개');
|
|
|
|
} catch (error) {
|
|
console.error('노트 로드 실패:', error);
|
|
this.error = '노트를 불러오는데 실패했습니다: ' + error.message;
|
|
this.notes = [];
|
|
} finally {
|
|
this.loading = false;
|
|
}
|
|
},
|
|
|
|
// 검색 디바운스
|
|
debounceSearch() {
|
|
clearTimeout(this.searchTimeout);
|
|
this.searchTimeout = setTimeout(() => {
|
|
this.loadNotes();
|
|
}, 500);
|
|
},
|
|
|
|
// 노트 새로고침
|
|
async refreshNotes() {
|
|
await Promise.all([
|
|
this.loadStats(),
|
|
this.loadNotes()
|
|
]);
|
|
},
|
|
|
|
// 새 노트 생성
|
|
createNewNote() {
|
|
window.location.href = '/note-editor.html';
|
|
},
|
|
|
|
// 노트 보기 (뷰어 페이지로 이동)
|
|
viewNote(noteId) {
|
|
window.location.href = `/viewer.html?type=note&id=${noteId}`;
|
|
},
|
|
|
|
// 노트 편집
|
|
editNote(noteId) {
|
|
window.location.href = `/note-editor.html?id=${noteId}`;
|
|
},
|
|
|
|
// 노트 삭제
|
|
async deleteNote(note) {
|
|
if (!confirm(`"${note.title}" 노트를 삭제하시겠습니까?`)) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch(`/api/note-documents/${note.id}`, {
|
|
method: 'DELETE',
|
|
headers: {
|
|
'Authorization': `Bearer ${localStorage.getItem('access_token')}`
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
this.showNotification('노트가 삭제되었습니다', 'success');
|
|
await this.refreshNotes();
|
|
} else {
|
|
throw new Error('삭제 실패');
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('노트 삭제 실패:', error);
|
|
this.showNotification('노트 삭제에 실패했습니다: ' + error.message, 'error');
|
|
}
|
|
},
|
|
|
|
// 노트 타입 라벨
|
|
getNoteTypeLabel(type) {
|
|
const labels = {
|
|
'note': '일반',
|
|
'research': '연구',
|
|
'summary': '요약',
|
|
'idea': '아이디어',
|
|
'guide': '가이드',
|
|
'reference': '참고'
|
|
};
|
|
return labels[type] || type;
|
|
},
|
|
|
|
// 날짜 포맷팅
|
|
formatDate(dateString) {
|
|
if (!dateString) return '';
|
|
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', {
|
|
year: 'numeric',
|
|
month: 'short',
|
|
day: 'numeric'
|
|
});
|
|
}
|
|
},
|
|
|
|
// 알림 표시
|
|
showNotification(message, type = 'info') {
|
|
console.log(`${type.toUpperCase()}: ${message}`);
|
|
|
|
// 간단한 토스트 알림 생성
|
|
const toast = document.createElement('div');
|
|
toast.className = `fixed top-4 right-4 px-6 py-3 rounded-lg text-white z-50 ${
|
|
type === 'success' ? 'bg-green-600' :
|
|
type === 'error' ? 'bg-red-600' : 'bg-blue-600'
|
|
}`;
|
|
toast.textContent = message;
|
|
|
|
document.body.appendChild(toast);
|
|
|
|
// 3초 후 제거
|
|
setTimeout(() => {
|
|
if (toast.parentNode) {
|
|
toast.parentNode.removeChild(toast);
|
|
}
|
|
}, 3000);
|
|
},
|
|
|
|
// === 일괄 선택 관련 메서드 ===
|
|
|
|
// 노트 선택/해제
|
|
toggleNoteSelection(noteId) {
|
|
const index = this.selectedNotes.indexOf(noteId);
|
|
if (index > -1) {
|
|
this.selectedNotes.splice(index, 1);
|
|
} else {
|
|
this.selectedNotes.push(noteId);
|
|
}
|
|
},
|
|
|
|
// 선택 해제
|
|
clearSelection() {
|
|
this.selectedNotes = [];
|
|
this.bulkNotebookId = '';
|
|
},
|
|
|
|
// 선택된 노트들을 노트북에 할당
|
|
async assignToNotebook() {
|
|
if (!this.bulkNotebookId || this.selectedNotes.length === 0) {
|
|
this.showNotification('노트북을 선택하고 노트를 선택해주세요.', 'error');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
// 각 노트를 업데이트
|
|
const updatePromises = this.selectedNotes.map(noteId =>
|
|
this.api.put(`/note-documents/${noteId}`, { notebook_id: this.bulkNotebookId })
|
|
);
|
|
|
|
await Promise.all(updatePromises);
|
|
|
|
this.showNotification(`${this.selectedNotes.length}개 노트가 노트북에 할당되었습니다.`, 'success');
|
|
|
|
// 선택 해제 및 새로고침
|
|
this.clearSelection();
|
|
await this.loadNotes();
|
|
|
|
} catch (error) {
|
|
console.error('노트북 할당 실패:', error);
|
|
this.showNotification('노트북 할당에 실패했습니다.', 'error');
|
|
}
|
|
},
|
|
|
|
// === 노트북 생성 관련 메서드 ===
|
|
|
|
// 노트북 생성 모달 닫기
|
|
closeCreateNotebookModal() {
|
|
this.showCreateNotebookModal = false;
|
|
this.newNotebookForm = {
|
|
name: '',
|
|
description: '',
|
|
color: '#3B82F6',
|
|
icon: 'book'
|
|
};
|
|
},
|
|
|
|
// 노트북 생성 및 노트 할당
|
|
async createNotebookAndAssign() {
|
|
if (!this.newNotebookForm.name.trim()) {
|
|
this.showNotification('노트북 이름을 입력해주세요.', 'error');
|
|
return;
|
|
}
|
|
|
|
this.creatingNotebook = true;
|
|
|
|
try {
|
|
// 1. 노트북 생성
|
|
const newNotebook = await this.api.post('/notebooks/', this.newNotebookForm);
|
|
console.log('📚 새 노트북 생성됨:', newNotebook.name);
|
|
|
|
// 2. 선택된 노트들이 있으면 할당
|
|
if (this.selectedNotes.length > 0) {
|
|
const updatePromises = this.selectedNotes.map(noteId =>
|
|
this.api.put(`/note-documents/${noteId}`, { notebook_id: newNotebook.id })
|
|
);
|
|
|
|
await Promise.all(updatePromises);
|
|
console.log(`📝 ${this.selectedNotes.length}개 노트가 새 노트북에 할당됨`);
|
|
}
|
|
|
|
this.showNotification(
|
|
`노트북 "${newNotebook.name}"이 생성되었습니다.${this.selectedNotes.length > 0 ? ` ${this.selectedNotes.length}개 노트가 할당되었습니다.` : ''}`,
|
|
'success'
|
|
);
|
|
|
|
// 3. 정리 및 새로고침
|
|
this.closeCreateNotebookModal();
|
|
this.clearSelection();
|
|
await this.loadNotebooks();
|
|
await this.loadNotes();
|
|
|
|
} catch (error) {
|
|
console.error('노트북 생성 실패:', error);
|
|
this.showNotification('노트북 생성에 실패했습니다.', 'error');
|
|
} finally {
|
|
this.creatingNotebook = false;
|
|
}
|
|
}
|
|
});
|
|
|
|
// 페이지 로드 시 초기화
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
console.log('📄 Notes 페이지 로드됨');
|
|
});
|