// 스토리 읽기 애플리케이션 function storyReaderApp() { return { // 상태 변수들 currentUser: null, loading: true, error: null, // 스토리 데이터 selectedTree: null, canonicalNodes: [], currentChapter: null, currentChapterIndex: 0, // URL 파라미터 treeId: null, nodeId: null, chapterIndex: null, // 편집 관련 showEditModal: false, editingChapter: null, editEditor: null, saving: false, // 로그인 관련 showLoginModal: false, loginForm: { email: '', password: '' }, loginError: '', loginLoading: false, // 초기화 async init() { console.log('🚀 스토리 리더 초기화 시작'); // URL 파라미터 파싱 this.parseUrlParams(); // 사용자 인증 확인 await this.checkAuth(); if (this.currentUser) { await this.loadStoryData(); } }, // URL 파라미터 파싱 parseUrlParams() { const urlParams = new URLSearchParams(window.location.search); this.treeId = urlParams.get('treeId'); this.nodeId = urlParams.get('nodeId'); this.chapterIndex = parseInt(urlParams.get('index')) || 0; console.log('📖 URL 파라미터:', { treeId: this.treeId, nodeId: this.nodeId, chapterIndex: this.chapterIndex }); }, // 인증 확인 async checkAuth() { try { this.currentUser = await window.api.getCurrentUser(); console.log('✅ 사용자 인증됨:', this.currentUser?.email); } catch (error) { console.log('❌ 인증 실패:', error); this.currentUser = null; } }, // 스토리 데이터 로드 async loadStoryData() { if (!this.treeId) { this.error = '트리 ID가 필요합니다'; this.loading = false; return; } try { this.loading = true; this.error = null; // 트리 정보 로드 this.selectedTree = await window.api.getMemoTree(this.treeId); console.log('📚 트리 로드됨:', this.selectedTree.title); // 트리의 모든 노드 로드 const allNodes = await window.api.getMemoTreeNodes(this.treeId); // 정사 노드만 필터링하고 순서대로 정렬 this.canonicalNodes = allNodes .filter(node => node.is_canonical) .sort((a, b) => (a.canonical_order || 0) - (b.canonical_order || 0)); console.log('📝 정사 노드 수:', this.canonicalNodes.length); if (this.canonicalNodes.length === 0) { this.error = '정사로 설정된 노드가 없습니다'; this.loading = false; return; } // 현재 챕터 설정 this.setCurrentChapter(); this.loading = false; } catch (error) { console.error('❌ 스토리 로드 실패:', error); this.error = '스토리를 불러오는데 실패했습니다'; this.loading = false; } }, // 현재 챕터 설정 setCurrentChapter() { if (this.nodeId) { // 특정 노드 ID로 찾기 const index = this.canonicalNodes.findIndex(node => node.id === this.nodeId); if (index !== -1) { this.currentChapterIndex = index; } } else if (this.chapterIndex >= 0 && this.chapterIndex < this.canonicalNodes.length) { // 인덱스로 설정 this.currentChapterIndex = this.chapterIndex; } else { // 기본값: 첫 번째 챕터 this.currentChapterIndex = 0; } this.currentChapter = this.canonicalNodes[this.currentChapterIndex]; console.log('📖 현재 챕터:', this.currentChapter?.title, `(${this.currentChapterIndex + 1}/${this.canonicalNodes.length})`); }, // 계산된 속성들 get totalChapters() { return this.canonicalNodes.length; }, get hasPreviousChapter() { return this.currentChapterIndex > 0; }, get hasNextChapter() { return this.currentChapterIndex < this.canonicalNodes.length - 1; }, get previousChapter() { return this.hasPreviousChapter ? this.canonicalNodes[this.currentChapterIndex - 1] : null; }, get nextChapter() { return this.hasNextChapter ? this.canonicalNodes[this.currentChapterIndex + 1] : null; }, // 네비게이션 함수들 goToPreviousChapter() { if (this.hasPreviousChapter) { this.currentChapterIndex--; this.currentChapter = this.canonicalNodes[this.currentChapterIndex]; this.updateUrl(); this.scrollToTop(); } }, goToNextChapter() { if (this.hasNextChapter) { this.currentChapterIndex++; this.currentChapter = this.canonicalNodes[this.currentChapterIndex]; this.updateUrl(); this.scrollToTop(); } }, goBackToStoryView() { window.location.href = `story-view.html?treeId=${this.treeId}`; }, editChapter() { if (this.currentChapter) { this.editingChapter = { ...this.currentChapter }; // 복사본 생성 this.showEditModal = true; console.log('✅ 편집 모달 열림 (Textarea 방식)'); } }, // 편집 취소 cancelEdit() { this.showEditModal = false; this.editingChapter = null; console.log('✅ 편집 취소됨 (Textarea 방식)'); }, // 편집 저장 async saveEdit() { if (!this.editingChapter) { console.warn('⚠️ 편집 중인 챕터가 없습니다'); return; } try { this.saving = true; // 단어 수 계산 const content = this.editingChapter.content || ''; let wordCount = 0; if (content && content.trim()) { const words = content.trim().split(/\s+/); wordCount = words.filter(word => word.length > 0).length; } this.editingChapter.word_count = wordCount; // API로 저장 const updateData = { title: this.editingChapter.title, content: this.editingChapter.content, word_count: this.editingChapter.word_count }; await window.api.updateMemoNode(this.editingChapter.id, updateData); // 현재 챕터 업데이트 this.currentChapter.title = this.editingChapter.title; this.currentChapter.content = this.editingChapter.content; this.currentChapter.word_count = this.editingChapter.word_count; // 정사 노드 목록에서도 업데이트 const nodeIndex = this.canonicalNodes.findIndex(node => node.id === this.editingChapter.id); if (nodeIndex !== -1) { this.canonicalNodes[nodeIndex].title = this.editingChapter.title; this.canonicalNodes[nodeIndex].content = this.editingChapter.content; this.canonicalNodes[nodeIndex].word_count = this.editingChapter.word_count; } console.log('✅ 챕터 저장 완료'); this.cancelEdit(); } catch (error) { console.error('❌ 저장 실패:', error); alert('저장 중 오류가 발생했습니다: ' + error.message); } finally { this.saving = false; } }, // URL 업데이트 updateUrl() { const url = new URL(window.location); url.searchParams.set('nodeId', this.currentChapter.id); url.searchParams.set('index', this.currentChapterIndex.toString()); window.history.replaceState({}, '', url); }, // 페이지 상단으로 스크롤 scrollToTop() { window.scrollTo({ top: 0, behavior: 'smooth' }); }, // 인쇄 printChapter() { window.print(); }, // 콘텐츠 포맷팅 formatContent(content) { if (!content) return ''; // 마크다운 스타일 간단 변환 return content .replace(/\n\n/g, '
')
.replace(/\n/g, '
')
.replace(/^/, '
') .replace(/$/, '
') .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\*(.*?)\*/g, '$1'); }, // 노드 타입 라벨 getNodeTypeLabel(nodeType) { const labels = { 'memo': '메모', 'folder': '폴더', 'chapter': '챕터', 'character': '캐릭터', 'plot': '플롯' }; return labels[nodeType] || nodeType; }, // 상태 라벨 getStatusLabel(status) { const labels = { 'draft': '초안', 'writing': '작성중', 'review': '검토중', 'complete': '완료' }; return labels[status] || status; }, // 로그인 관련 openLoginModal() { this.showLoginModal = true; this.loginError = ''; }, async handleLogin() { try { this.loginLoading = true; this.loginError = ''; const result = await window.api.login(this.loginForm.email, this.loginForm.password); if (result.access_token) { this.currentUser = result.user; this.showLoginModal = false; this.loginForm = { email: '', password: '' }; // 로그인 후 스토리 데이터 로드 await this.loadStoryData(); } } catch (error) { console.error('❌ 로그인 실패:', error); this.loginError = '로그인에 실패했습니다. 이메일과 비밀번호를 확인해주세요.'; } finally { this.loginLoading = false; } } }; }