🎯 주요 기능: - 하이라이트 메모 내용 별도 검색 (highlight_note 타입) - PDF/HTML 본문 전체 텍스트 검색 (OCR 데이터 활용) - 검색 결과 미리보기 모달 (전체 내용 로드) - 메모 트리 노드 검색 지원 - 노트 문서 통합 검색 🔧 백엔드 개선: - search_highlight_notes: 하이라이트 메모 내용 검색 - search_document_content: HTML/PDF 본문 검색 (BeautifulSoup) - search_memo_nodes: 메모 트리 노드 검색 - search_note_documents: 노트 문서 검색 - extract_search_context: 검색어 주변 컨텍스트 추출 🎨 프론트엔드 기능: - 통합 검색 UI (/search.html) 완전 구현 - 검색 필터: 문서/노트/메모/하이라이트/메모/본문 - 미리보기 모달: 전체 내용 로드 및 표시 - 검색 결과 하이라이트 및 컨텍스트 표시 - 타입별 배지 및 관련도 점수 표시 📱 사용자 경험: - 실시간 검색 디바운스 (500ms) - 검색어 자동완성 제안 - 검색 통계 및 성능 표시 - 빠른 검색 예시 버튼 - 새 탭에서 결과 열기 🔗 네비게이션 통합: - 헤더에 '통합 검색' 링크 추가 - 페이지별 활성 상태 관리
164 lines
5.5 KiB
JavaScript
164 lines
5.5 KiB
JavaScript
/**
|
|
* 공통 헤더 로더
|
|
* 모든 페이지에서 동일한 헤더를 로드하기 위한 유틸리티
|
|
*/
|
|
|
|
class HeaderLoader {
|
|
constructor() {
|
|
this.headerLoaded = false;
|
|
}
|
|
|
|
/**
|
|
* 헤더 HTML을 로드하고 삽입
|
|
*/
|
|
async loadHeader(targetSelector = '#header-container') {
|
|
if (this.headerLoaded) {
|
|
console.log('✅ 헤더가 이미 로드됨');
|
|
return;
|
|
}
|
|
|
|
try {
|
|
console.log('🔄 헤더 로딩 중...');
|
|
|
|
const response = await fetch('components/header.html?v=2025012352');
|
|
if (!response.ok) {
|
|
throw new Error(`헤더 로드 실패: ${response.status}`);
|
|
}
|
|
|
|
const headerHtml = await response.text();
|
|
|
|
// 헤더 컨테이너 찾기
|
|
const container = document.querySelector(targetSelector);
|
|
if (!container) {
|
|
throw new Error(`헤더 컨테이너를 찾을 수 없음: ${targetSelector}`);
|
|
}
|
|
|
|
// 헤더 HTML 삽입
|
|
container.innerHTML = headerHtml;
|
|
|
|
this.headerLoaded = true;
|
|
console.log('✅ 헤더 로드 완료');
|
|
|
|
// 헤더 로드 완료 이벤트 발생
|
|
document.dispatchEvent(new CustomEvent('headerLoaded'));
|
|
|
|
} catch (error) {
|
|
console.error('❌ 헤더 로드 오류:', error);
|
|
this.showFallbackHeader(targetSelector);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 헤더 로드 실패 시 폴백 헤더 표시
|
|
*/
|
|
showFallbackHeader(targetSelector) {
|
|
const container = document.querySelector(targetSelector);
|
|
if (container) {
|
|
container.innerHTML = `
|
|
<header class="bg-white border-b border-gray-200 shadow-sm">
|
|
<div class="max-w-full mx-auto px-4 sm:px-6 lg:px-8">
|
|
<div class="flex justify-between items-center h-16">
|
|
<div class="flex items-center space-x-2">
|
|
<i class="fas fa-book text-blue-600 text-xl"></i>
|
|
<h1 class="text-xl font-bold text-gray-900">Document Server</h1>
|
|
</div>
|
|
<nav class="flex space-x-6">
|
|
<a href="index.html" class="text-gray-600 hover:text-blue-600">📁 문서 관리</a>
|
|
<a href="memo-tree.html" class="text-gray-600 hover:text-blue-600">🌳 메모장</a>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
`;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 현재 페이지 정보 가져오기
|
|
*/
|
|
getCurrentPageInfo() {
|
|
const path = window.location.pathname;
|
|
const filename = path.split('/').pop().replace('.html', '') || 'index';
|
|
|
|
const pageInfo = {
|
|
filename,
|
|
isDocumentPage: ['index', 'hierarchy'].includes(filename),
|
|
isMemoPage: ['memo-tree', 'story-view'].includes(filename)
|
|
};
|
|
|
|
return pageInfo;
|
|
}
|
|
|
|
/**
|
|
* 페이지별 활성 상태 업데이트
|
|
*/
|
|
updateActiveStates() {
|
|
const pageInfo = this.getCurrentPageInfo();
|
|
|
|
// 모든 활성 클래스 제거
|
|
document.querySelectorAll('.nav-link, .nav-dropdown-item').forEach(item => {
|
|
item.classList.remove('active');
|
|
});
|
|
|
|
// 현재 페이지에 따라 활성 상태 설정
|
|
console.log('현재 페이지:', pageInfo.filename);
|
|
|
|
if (pageInfo.isDocumentPage) {
|
|
const docLink = document.getElementById('doc-nav-link');
|
|
if (docLink) {
|
|
docLink.classList.add('active');
|
|
console.log('문서 관리 메뉴 활성화');
|
|
}
|
|
}
|
|
|
|
if (pageInfo.isMemoPage) {
|
|
const memoLink = document.getElementById('memo-nav-link');
|
|
if (memoLink) {
|
|
memoLink.classList.add('active');
|
|
console.log('메모장 메뉴 활성화');
|
|
}
|
|
}
|
|
|
|
// 특정 페이지 드롭다운 아이템 활성화
|
|
const pageItemMap = {
|
|
'index': 'index-nav-item',
|
|
'hierarchy': 'hierarchy-nav-item',
|
|
'memo-tree': 'memo-tree-nav-item',
|
|
'story-view': 'story-view-nav-item',
|
|
'search': 'search-nav-link',
|
|
'notes': 'notes-nav-link',
|
|
'notebooks': 'notebooks-nav-item',
|
|
'note-editor': 'note-editor-nav-item'
|
|
};
|
|
|
|
const itemId = pageItemMap[pageInfo.filename];
|
|
if (itemId) {
|
|
const item = document.getElementById(itemId);
|
|
if (item) {
|
|
item.classList.add('active');
|
|
console.log(`${pageInfo.filename} 페이지 아이템 활성화`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 전역 인스턴스 생성
|
|
window.headerLoader = new HeaderLoader();
|
|
|
|
// DOM 로드 완료 시 자동 초기화
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
window.headerLoader.loadHeader();
|
|
});
|
|
|
|
// 헤더 로드 완료 후 활성 상태 업데이트
|
|
document.addEventListener('headerLoaded', () => {
|
|
setTimeout(() => {
|
|
window.headerLoader.updateActiveStates();
|
|
|
|
// 사용자 메뉴 초기 상태 설정 (로그아웃 상태로 시작)
|
|
if (typeof window.updateUserMenu === 'function') {
|
|
window.updateUserMenu(null);
|
|
}
|
|
}, 100);
|
|
});
|