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