스토리 뷰 페이지 헤더 z-index 충돌 문제 해결
- 메인 컨테이너 padding-top을 pt-4에서 pt-20으로 증가 - 드롭다운 z-index를 z-[60]으로 설정하여 헤더보다 높은 우선순위 부여 - 스토리 선택 드롭다운이 정상적으로 작동하도록 수정 - 디버깅용 코드 정리
This commit is contained in:
@@ -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 '';
|
||||
|
||||
@@ -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 = [];
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user