스토리 뷰 페이지 헤더 z-index 충돌 문제 해결

- 메인 컨테이너 padding-top을 pt-4에서 pt-20으로 증가
- 드롭다운 z-index를 z-[60]으로 설정하여 헤더보다 높은 우선순위 부여
- 스토리 선택 드롭다운이 정상적으로 작동하도록 수정
- 디버깅용 코드 정리
This commit is contained in:
Hyungi Ahn
2025-09-04 10:22:43 +09:00
parent 3ba804276c
commit 43e7466195
11 changed files with 709 additions and 35 deletions

View File

@@ -11,6 +11,14 @@ window.pdfManagerApp = () => ({
isAuthenticated: false,
currentUser: null,
// PDF 미리보기 상태
showPreviewModal: false,
previewPdf: null,
pdfPreviewSrc: '',
pdfPreviewLoading: false,
pdfPreviewError: false,
pdfPreviewLoaded: false,
// 초기화
async init() {
console.log('🚀 PDF Manager App 초기화 시작');
@@ -213,6 +221,56 @@ window.pdfManagerApp = () => ({
}
},
// ==================== PDF 미리보기 관련 ====================
async previewPDF(pdf) {
console.log('👁️ PDF 미리보기:', pdf.title);
this.previewPdf = pdf;
this.showPreviewModal = true;
this.pdfPreviewLoading = true;
this.pdfPreviewError = false;
this.pdfPreviewLoaded = false;
try {
const token = localStorage.getItem('access_token');
if (!token || token === 'null' || token === null) {
throw new Error('인증 토큰이 없습니다. 다시 로그인해주세요.');
}
// PDF 미리보기 URL 설정
this.pdfPreviewSrc = `/api/documents/${pdf.id}/pdf?_token=${encodeURIComponent(token)}`;
console.log('✅ PDF 미리보기 준비 완료:', this.pdfPreviewSrc);
} catch (error) {
console.error('❌ PDF 미리보기 로드 실패:', error);
this.pdfPreviewError = true;
this.showNotification('PDF 미리보기 로드에 실패했습니다: ' + error.message, 'error');
} finally {
this.pdfPreviewLoading = false;
}
},
closePreview() {
this.showPreviewModal = false;
this.previewPdf = null;
this.pdfPreviewSrc = '';
this.pdfPreviewLoading = false;
this.pdfPreviewError = false;
this.pdfPreviewLoaded = false;
},
handlePdfPreviewError() {
console.error('❌ PDF 미리보기 iframe 로드 오류');
this.pdfPreviewError = true;
this.pdfPreviewLoading = false;
},
async retryPdfPreview() {
if (this.previewPdf) {
await this.previewPDF(this.previewPdf);
}
},
// 날짜 포맷팅
formatDate(dateString) {
if (!dateString) return '';

View File

@@ -17,7 +17,10 @@ window.storyViewApp = function() {
// UI 상태
showLoginModal: false,
showEditModal: false,
editingNode: null,
editingNode: {
title: '',
content: ''
},
// 로그인 폼 상태
loginForm: {
@@ -79,11 +82,22 @@ window.storyViewApp = function() {
async loadUserTrees() {
try {
console.log('📊 사용자 트리 목록 로딩...');
console.log('🔍 API 객체 확인:', window.api);
console.log('🔍 getUserMemoTrees 함수 확인:', typeof window.api?.getUserMemoTrees);
const trees = await window.api.getUserMemoTrees();
this.userTrees = trees || [];
console.log(`${this.userTrees.length}개 트리 로드 완료`);
console.log('📋 트리 목록:', this.userTrees);
// Alpine.js 반응성 업데이트를 위한 약간의 지연 후 URL 파라미터 확인
setTimeout(() => {
this.checkUrlParams();
}, 100);
} catch (error) {
console.error('❌ 트리 목록 로드 실패:', error);
console.error('❌ 에러 상세:', error.message);
console.error('❌ 에러 스택:', error.stack);
this.userTrees = [];
}
},

View File

@@ -24,9 +24,15 @@ class DocumentLoader {
document.title = `${noteDocument.title} - Document Server`;
// 노트 내용을 HTML로 설정
const contentElement = document.getElementById('document-content');
if (contentElement && noteDocument.content) {
contentElement.innerHTML = noteDocument.content;
const noteContentElement = document.getElementById('note-content');
if (noteContentElement && noteDocument.content) {
noteContentElement.innerHTML = noteDocument.content;
} else {
// 폴백: document-content 사용
const contentElement = document.getElementById('document-content');
if (contentElement && noteDocument.content) {
contentElement.innerHTML = noteDocument.content;
}
}
console.log('📝 노트 로드 완료:', noteDocument.title);
@@ -46,25 +52,28 @@ class DocumentLoader {
// 백엔드에서 문서 정보 가져오기 (캐싱 적용)
const docData = await this.cachedApi.get(`/documents/${documentId}`, { content_type: 'document' }, { category: 'document' });
// HTML 파일 경로 구성 (백엔드 서버를 통해 접근)
const htmlPath = docData.html_path;
const fileName = htmlPath.split('/').pop();
const response = await fetch(`http://localhost:24102/uploads/documents/${fileName}`);
if (!response.ok) {
throw new Error('문서 파일을 불러올 수 없습니다');
}
const htmlContent = await response.text();
document.getElementById('document-content').innerHTML = htmlContent;
// 페이지 제목 업데이트
document.title = `${docData.title} - Document Server`;
// 문서 내 스크립트 오류 방지를 위한 전역 함수들 정의
this.setupDocumentScriptHandlers();
// PDF 문서가 아닌 경우에만 HTML 로드
if (!docData.pdf_path && docData.html_path) {
// HTML 파일 경로 구성 (백엔드 서버를 통해 접근)
const htmlPath = docData.html_path;
const fileName = htmlPath.split('/').pop();
const response = await fetch(`http://localhost:24102/uploads/documents/${fileName}`);
if (!response.ok) {
throw new Error('문서 파일을 불러올 수 없습니다');
}
const htmlContent = await response.text();
document.getElementById('document-content').innerHTML = htmlContent;
// 문서 내 스크립트 오류 방지를 위한 전역 함수들 정의
this.setupDocumentScriptHandlers();
}
console.log('✅ 문서 로드 완료:', docData.title);
console.log('✅ 문서 로드 완료:', docData.title, docData.pdf_path ? '(PDF)' : '(HTML)');
return docData;
} catch (error) {

View File

@@ -11,6 +11,27 @@ window.documentViewer = () => ({
contentType: 'document', // 'document' 또는 'note'
navigation: null,
// ==================== PDF 뷰어 상태 ====================
pdfSrc: '',
pdfLoading: false,
pdfError: false,
pdfLoaded: false,
// ==================== PDF 검색 상태 ====================
showPdfSearchModal: false,
pdfSearchQuery: '',
pdfSearchResults: [],
pdfSearchLoading: false,
// ==================== PDF.js 뷰어 상태 ====================
pdfDocument: null,
currentPage: 1,
totalPages: 0,
pdfScale: 1.0,
pdfCanvas: null,
pdfContext: null,
pdfTextContent: [],
// ==================== 데이터 상태 ====================
highlights: [],
notes: [],
@@ -280,6 +301,11 @@ window.documentViewer = () => ({
this.document = await this.documentLoader.loadDocument(this.documentId);
// 네비게이션 별도 로드
this.navigation = await this.documentLoader.loadNavigation(this.documentId);
// PDF 문서인 경우 PDF 뷰어 준비
if (this.document && this.document.pdf_path) {
await this.loadPdfViewer();
}
}
// 관련 데이터 병렬 로드
@@ -1825,6 +1851,217 @@ window.documentViewer = () => ({
}
},
// ==================== PDF 뷰어 관련 ====================
async loadPdfViewer() {
console.log('📄 PDF 뷰어 로드 시작');
this.pdfLoading = true;
this.pdfError = false;
this.pdfLoaded = false;
try {
const token = localStorage.getItem('access_token');
if (!token || token === 'null' || token === null) {
throw new Error('인증 토큰이 없습니다. 다시 로그인해주세요.');
}
// PDF 뷰어 URL 설정 (토큰 포함)
this.pdfSrc = `/api/documents/${this.documentId}/pdf?_token=${encodeURIComponent(token)}`;
console.log('✅ PDF 뷰어 준비 완료:', this.pdfSrc);
// PDF.js로 PDF 로드
await this.loadPdfWithPdfJs();
} catch (error) {
console.error('❌ PDF 뷰어 로드 실패:', error);
this.pdfError = true;
} finally {
this.pdfLoading = false;
}
},
async loadPdfWithPdfJs() {
try {
// PDF.js 워커 설정
if (typeof pdfjsLib !== 'undefined') {
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
console.log('📄 PDF.js로 PDF 로드 시작:', this.pdfSrc);
// PDF 문서 로드
const loadingTask = pdfjsLib.getDocument(this.pdfSrc);
this.pdfDocument = await loadingTask.promise;
this.totalPages = this.pdfDocument.numPages;
this.currentPage = 1;
console.log(`✅ PDF 로드 완료: ${this.totalPages} 페이지`);
// 캔버스 초기화
this.initPdfCanvas();
// 첫 페이지 렌더링
await this.renderPdfPage(1);
this.pdfLoaded = true;
} else {
throw new Error('PDF.js 라이브러리가 로드되지 않았습니다.');
}
} catch (error) {
console.error('❌ PDF.js 로드 실패:', error);
throw error;
}
},
initPdfCanvas() {
this.pdfCanvas = document.getElementById('pdf-canvas');
if (this.pdfCanvas) {
this.pdfContext = this.pdfCanvas.getContext('2d');
}
},
async renderPdfPage(pageNum) {
if (!this.pdfDocument || !this.pdfCanvas) return;
try {
console.log(`📄 페이지 ${pageNum} 렌더링 시작`);
const page = await this.pdfDocument.getPage(pageNum);
const viewport = page.getViewport({ scale: this.pdfScale });
// 캔버스 크기 설정
this.pdfCanvas.height = viewport.height;
this.pdfCanvas.width = viewport.width;
// 페이지 렌더링
const renderContext = {
canvasContext: this.pdfContext,
viewport: viewport
};
await page.render(renderContext).promise;
// 텍스트 내용 추출 (검색용)
const textContent = await page.getTextContent();
this.pdfTextContent[pageNum] = textContent.items.map(item => item.str).join(' ');
console.log(`✅ 페이지 ${pageNum} 렌더링 완료`);
} catch (error) {
console.error(`❌ 페이지 ${pageNum} 렌더링 실패:`, error);
}
},
handlePdfError() {
console.error('❌ PDF iframe 로드 오류');
this.pdfError = true;
this.pdfLoading = false;
},
async retryPdfLoad() {
console.log('🔄 PDF 재로드 시도');
await this.loadPdfViewer();
},
// ==================== PDF 검색 관련 ====================
openPdfSearchModal() {
this.showPdfSearchModal = true;
this.pdfSearchQuery = '';
this.pdfSearchResults = [];
// 모달이 열린 후 입력 필드에 포커스
setTimeout(() => {
const searchInput = document.querySelector('input[x-ref="searchInput"]');
if (searchInput) {
searchInput.focus();
searchInput.select();
}
}, 100);
},
async searchInPdf() {
if (!this.pdfSearchQuery.trim()) {
alert('검색어를 입력해주세요.');
return;
}
console.log('🔍 PDF 검색 시작:', this.pdfSearchQuery);
this.pdfSearchLoading = true;
this.pdfSearchResults = [];
try {
// 백엔드 API를 통해 PDF 내용 검색
const searchResults = await this.api.get(
`/documents/${this.documentId}/search-in-content?q=${encodeURIComponent(this.pdfSearchQuery)}`
);
console.log('✅ PDF 검색 결과:', searchResults);
if (searchResults.matches && searchResults.matches.length > 0) {
this.pdfSearchResults = searchResults.matches.map(match => ({
page: match.page || 1,
context: match.context || match.text || this.pdfSearchQuery,
position: match.position || 0
}));
console.log(`📄 ${this.pdfSearchResults.length}개의 검색 결과 발견`);
if (this.pdfSearchResults.length === 0) {
alert('검색 결과를 찾을 수 없습니다.');
}
} else {
alert('검색 결과를 찾을 수 없습니다.');
}
} catch (error) {
console.error('❌ PDF 검색 실패:', error);
alert('PDF 검색 중 오류가 발생했습니다: ' + error.message);
} finally {
this.pdfSearchLoading = false;
}
},
jumpToPdfResult(result) {
console.log('📍 PDF 결과로 이동:', result);
// PDF URL에 페이지 번호 추가하여 해당 페이지로 이동
const token = localStorage.getItem('access_token');
let newPdfSrc = `/api/documents/${this.documentId}/pdf?_token=${encodeURIComponent(token)}`;
// 페이지 번호가 있으면 URL 프래그먼트로 추가
if (result.page && result.page > 1) {
newPdfSrc += `#page=${result.page}`;
}
// PDF src 업데이트하여 해당 페이지로 이동
this.pdfSrc = newPdfSrc;
console.log(`📄 페이지 ${result.page}로 이동:`, newPdfSrc);
// 잠시 후 검색 기능 활성화
setTimeout(() => {
const iframe = document.querySelector('#pdf-viewer-iframe');
if (iframe && iframe.contentWindow) {
try {
iframe.contentWindow.focus();
// 브라우저 내장 검색 기능 활용
if (iframe.contentWindow.find) {
iframe.contentWindow.find(this.pdfSearchQuery);
} else {
// 대안: 사용자에게 수동 검색 안내
this.showSuccessMessage(`페이지 ${result.page}로 이동했습니다. Ctrl+F를 눌러 "${this.pdfSearchQuery}"를 검색하세요.`);
}
} catch (e) {
console.warn('PDF iframe 접근 제한:', e);
this.showSuccessMessage(`페이지 ${result.page}로 이동했습니다. Ctrl+F를 눌러 "${this.pdfSearchQuery}"를 검색하세요.`);
}
}
}, 1000);
// 모달 닫기
this.showPdfSearchModal = false;
},
async editNote(noteId, currentContent) {
console.log('✏️ 메모 편집:', noteId);
console.log('🔍 HighlightManager 상태:', this.highlightManager);