Fix: 업로드 및 API 연결 문제 해결
- FastAPI 라우터에서 슬래시 문제로 인한 307 리다이렉트 수정 - Nginx 프록시 설정에서 경로 중복 문제 해결 - 계정 관리 시스템 구현 (로그인, 사용자 관리, 권한 설정) - 노트북 연결 기능 수정 (notebook_id 필드 추가) - 메모 트리 UI 개선 (수평 레이아웃, 드래그 기능 제거) - 헤더 UI 개선 및 고정 위치 설정 - 백업/복원 스크립트 추가 - PDF 미리보기 토큰 인증 지원
This commit is contained in:
@@ -15,6 +15,7 @@ window.searchApp = function() {
|
||||
|
||||
// 필터링
|
||||
typeFilter: '', // '', 'document', 'note', 'memo', 'highlight'
|
||||
fileTypeFilter: '', // '', 'PDF', 'HTML'
|
||||
sortBy: 'relevance', // 'relevance', 'date_desc', 'date_asc', 'title'
|
||||
|
||||
// 검색 디바운스
|
||||
@@ -157,9 +158,43 @@ window.searchApp = function() {
|
||||
applyFilters() {
|
||||
let results = [...this.searchResults];
|
||||
|
||||
// 중복 ID 제거 (같은 문서의 document와 document_content가 중복될 수 있음)
|
||||
const uniqueResults = [];
|
||||
const seenIds = new Set();
|
||||
|
||||
results.forEach(result => {
|
||||
const uniqueKey = `${result.type}-${result.id}`;
|
||||
if (!seenIds.has(uniqueKey)) {
|
||||
seenIds.add(uniqueKey);
|
||||
uniqueResults.push({
|
||||
...result,
|
||||
unique_id: uniqueKey // Alpine.js x-for 키로 사용
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
results = uniqueResults;
|
||||
|
||||
// 타입 필터
|
||||
if (this.typeFilter) {
|
||||
results = results.filter(result => result.type === this.typeFilter);
|
||||
results = results.filter(result => {
|
||||
// 문서 타입은 document와 document_content 모두 포함
|
||||
if (this.typeFilter === 'document') {
|
||||
return result.type === 'document' || result.type === 'document_content';
|
||||
}
|
||||
// 하이라이트 타입은 highlight와 highlight_note 모두 포함
|
||||
if (this.typeFilter === 'highlight') {
|
||||
return result.type === 'highlight' || result.type === 'highlight_note';
|
||||
}
|
||||
return result.type === this.typeFilter;
|
||||
});
|
||||
}
|
||||
|
||||
// 파일 타입 필터
|
||||
if (this.fileTypeFilter) {
|
||||
results = results.filter(result => {
|
||||
return result.highlight_info?.file_type === this.fileTypeFilter;
|
||||
});
|
||||
}
|
||||
|
||||
// 정렬
|
||||
@@ -179,7 +214,7 @@ window.searchApp = function() {
|
||||
});
|
||||
|
||||
this.filteredResults = results;
|
||||
console.log('🔧 필터 적용 완료:', this.filteredResults.length, '개 결과');
|
||||
console.log('🔧 필터 적용 완료:', this.filteredResults.length, '개 결과 (타입:', this.typeFilter, ', 파일타입:', this.fileTypeFilter, ')');
|
||||
},
|
||||
|
||||
// URL 업데이트
|
||||
@@ -339,22 +374,23 @@ window.searchApp = function() {
|
||||
this.pdfLoaded = false;
|
||||
|
||||
try {
|
||||
// PDF 파일 존재 여부 먼저 확인
|
||||
const response = await fetch(`/api/documents/${documentId}/pdf`, {
|
||||
method: 'HEAD',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
}
|
||||
// PDF 파일 src 직접 설정 (HEAD 요청 대신)
|
||||
const token = localStorage.getItem('access_token');
|
||||
console.log('🔍 토큰 디버깅:', {
|
||||
token: token,
|
||||
tokenType: typeof token,
|
||||
tokenLength: token ? token.length : 0,
|
||||
isNull: token === null,
|
||||
isStringNull: token === 'null',
|
||||
localStorage: Object.keys(localStorage)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
// PDF 파일이 존재하면 src 설정
|
||||
const token = localStorage.getItem('token');
|
||||
this.pdfSrc = `/api/documents/${documentId}/pdf?_token=${encodeURIComponent(token)}`;
|
||||
console.log('PDF 미리보기 준비 완료:', this.pdfSrc);
|
||||
} else {
|
||||
throw new Error(`PDF 파일을 찾을 수 없습니다 (${response.status})`);
|
||||
if (!token || token === 'null' || token === null) {
|
||||
console.error('❌ 토큰 문제:', token);
|
||||
throw new Error('인증 토큰이 없습니다. 다시 로그인해주세요.');
|
||||
}
|
||||
this.pdfSrc = `/api/documents/${documentId}/pdf?_token=${encodeURIComponent(token)}`;
|
||||
console.log('✅ PDF 미리보기 준비 완료:', this.pdfSrc);
|
||||
} catch (error) {
|
||||
console.error('PDF 미리보기 로드 실패:', error);
|
||||
this.pdfError = true;
|
||||
@@ -370,6 +406,50 @@ window.searchApp = function() {
|
||||
this.pdfLoading = false;
|
||||
},
|
||||
|
||||
// PDF에서 검색어 찾기 (브라우저 내장 검색 활용)
|
||||
searchInPdf() {
|
||||
if (this.searchQuery && this.pdfLoaded) {
|
||||
// iframe 내에서 검색 실행 (Ctrl+F 시뮬레이션)
|
||||
const iframe = document.querySelector('#pdf-preview-iframe');
|
||||
if (iframe && iframe.contentWindow) {
|
||||
try {
|
||||
iframe.contentWindow.focus();
|
||||
// 브라우저 검색 창 열기 시도
|
||||
if (iframe.contentWindow.find) {
|
||||
iframe.contentWindow.find(this.searchQuery);
|
||||
} else {
|
||||
// 대안: 사용자에게 수동 검색 안내
|
||||
this.showNotification(`PDF에서 "${this.searchQuery}"를 찾으려면 Ctrl+F를 눌러주세요.`, 'info');
|
||||
}
|
||||
} catch (e) {
|
||||
// 보안상 직접 접근이 안 되는 경우, 사용자에게 안내
|
||||
this.showNotification(`PDF에서 "${this.searchQuery}"를 찾으려면 Ctrl+F를 눌러주세요.`, 'info');
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 알림 표시 (간단한 토스트)
|
||||
showNotification(message, type = 'info') {
|
||||
// 간단한 알림 구현 (실제로는 더 정교한 토스트 시스템을 사용할 수 있음)
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `fixed top-4 right-4 px-4 py-2 rounded-lg text-white z-50 ${
|
||||
type === 'info' ? 'bg-blue-500' :
|
||||
type === 'success' ? 'bg-green-500' :
|
||||
type === 'error' ? 'bg-red-500' : 'bg-gray-500'
|
||||
}`;
|
||||
notification.textContent = message;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// 3초 후 자동 제거
|
||||
setTimeout(() => {
|
||||
if (notification.parentNode) {
|
||||
notification.parentNode.removeChild(notification);
|
||||
}
|
||||
}, 3000);
|
||||
},
|
||||
|
||||
// HTML 미리보기 로드
|
||||
async loadHtmlPreview(documentId) {
|
||||
this.htmlLoading = true;
|
||||
@@ -385,7 +465,12 @@ window.searchApp = function() {
|
||||
const iframe = document.getElementById('htmlPreviewFrame');
|
||||
if (iframe) {
|
||||
// iframe src를 직접 설정 (인증 헤더 포함)
|
||||
const token = localStorage.getItem('token');
|
||||
const token = localStorage.getItem('access_token');
|
||||
console.log('🔍 HTML 미리보기 토큰:', token ? '있음' : '없음', token);
|
||||
if (!token || token === 'null' || token === null) {
|
||||
console.error('❌ HTML 미리보기 토큰 문제:', token);
|
||||
throw new Error('인증 토큰이 없습니다.');
|
||||
}
|
||||
iframe.src = `/api/documents/${documentId}/content?_token=${encodeURIComponent(token)}`;
|
||||
|
||||
// iframe 로드 완료 후 검색어 하이라이트
|
||||
|
||||
Reference in New Issue
Block a user