하이라이트 색상 문제 해결 및 다중 하이라이트 렌더링 개선
주요 수정사항: - 하이라이트 생성 시 color → highlight_color 필드명 수정으로 색상 전달 문제 해결 - 분홍색을 더 연하게 변경하여 글씨 가독성 향상 - 다중 하이라이트 렌더링을 위아래 균등 분할로 개선 - CSS highlight-span 클래스 추가 및 색상 적용 강화 - 하이라이트 생성/렌더링 과정에 상세한 디버깅 로그 추가 UI 개선: - 단일 하이라이트: 선택한 색상으로 정확히 표시 - 다중 하이라이트: 위아래로 균등하게 색상 분할 표시 - 메모 입력 모달에서 선택된 텍스트 표시 개선 버그 수정: - 프론트엔드-백엔드 API 스키마 불일치 해결 - CSS 스타일 우선순위 문제 해결 - 하이라이트 색상이 노랑색으로만 표시되던 문제 해결
This commit is contained in:
252
frontend/static/js/viewer/core/document-loader.js
Normal file
252
frontend/static/js/viewer/core/document-loader.js
Normal file
@@ -0,0 +1,252 @@
|
||||
/**
|
||||
* DocumentLoader 모듈
|
||||
* 문서/노트 로딩 및 네비게이션 관리
|
||||
*/
|
||||
class DocumentLoader {
|
||||
constructor(api) {
|
||||
this.api = api;
|
||||
// 캐싱된 API 사용 (사용 가능한 경우)
|
||||
this.cachedApi = window.cachedApi || api;
|
||||
console.log('📄 DocumentLoader 초기화 완료 (캐싱 API 적용)');
|
||||
}
|
||||
|
||||
/**
|
||||
* 노트 로드
|
||||
*/
|
||||
async loadNote(documentId) {
|
||||
try {
|
||||
console.log('📝 노트 로드 시작:', documentId);
|
||||
|
||||
// 백엔드에서 노트 정보 가져오기
|
||||
const noteDocument = await this.api.get(`/note-documents/${documentId}`);
|
||||
|
||||
// 노트 제목 설정
|
||||
document.title = `${noteDocument.title} - Document Server`;
|
||||
|
||||
// 노트 내용을 HTML로 설정
|
||||
const contentElement = document.getElementById('document-content');
|
||||
if (contentElement && noteDocument.content) {
|
||||
contentElement.innerHTML = noteDocument.content;
|
||||
}
|
||||
|
||||
console.log('📝 노트 로드 완료:', noteDocument.title);
|
||||
return noteDocument;
|
||||
|
||||
} catch (error) {
|
||||
console.error('노트 로드 실패:', error);
|
||||
throw new Error('노트를 불러올 수 없습니다');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 문서 로드 (실제 API 연동)
|
||||
*/
|
||||
async loadDocument(documentId) {
|
||||
try {
|
||||
// 백엔드에서 문서 정보 가져오기 (캐싱 적용)
|
||||
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();
|
||||
|
||||
console.log('✅ 문서 로드 완료:', docData.title);
|
||||
return docData;
|
||||
|
||||
} catch (error) {
|
||||
console.error('Document load error:', error);
|
||||
|
||||
// 백엔드 연결 실패시 목업 데이터로 폴백
|
||||
console.warn('Using fallback mock data');
|
||||
const mockDocument = {
|
||||
id: documentId,
|
||||
title: 'Document Server 테스트 문서',
|
||||
description: '하이라이트와 메모 기능을 테스트하기 위한 샘플 문서입니다.',
|
||||
uploader_name: '관리자'
|
||||
};
|
||||
|
||||
// 기본 HTML 내용 표시
|
||||
document.getElementById('document-content').innerHTML = `
|
||||
<h1>테스트 문서</h1>
|
||||
<p>이 문서는 Document Server의 하이라이트 및 메모 기능을 테스트하기 위한 샘플입니다.</p>
|
||||
<p>텍스트를 선택하면 하이라이트를 추가할 수 있습니다.</p>
|
||||
<h2>주요 기능</h2>
|
||||
<ul>
|
||||
<li>텍스트 선택 후 하이라이트 생성</li>
|
||||
<li>하이라이트에 메모 추가</li>
|
||||
<li>메모 검색 및 관리</li>
|
||||
<li>책갈피 기능</li>
|
||||
</ul>
|
||||
<h2>테스트 단락</h2>
|
||||
<p>이것은 하이라이트 테스트를 위한 긴 단락입니다. 이 텍스트를 선택하여 하이라이트를 만들어보세요.
|
||||
하이라이트를 만든 후에는 메모를 추가할 수 있습니다. 메모는 나중에 검색하고 편집할 수 있습니다.</p>
|
||||
<p>또 다른 단락입니다. 여러 개의 하이라이트를 만들어서 메모 기능을 테스트해보세요.
|
||||
각 하이라이트는 고유한 색상을 가질 수 있으며, 연결된 메모를 통해 중요한 정보를 기록할 수 있습니다.</p>
|
||||
`;
|
||||
|
||||
// 폴백 모드에서도 스크립트 핸들러 설정
|
||||
this.setupDocumentScriptHandlers();
|
||||
|
||||
return mockDocument;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 네비게이션 정보 로드
|
||||
*/
|
||||
async loadNavigation(documentId) {
|
||||
try {
|
||||
// CachedAPI의 getDocumentNavigation 메서드 사용
|
||||
const navigation = await this.api.getDocumentNavigation(documentId);
|
||||
console.log('📍 네비게이션 정보 로드됨:', navigation);
|
||||
return navigation;
|
||||
} catch (error) {
|
||||
console.error('❌ 네비게이션 정보 로드 실패:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* URL 파라미터에서 특정 텍스트 하이라이트 확인
|
||||
*/
|
||||
checkForTextHighlight() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const highlightText = urlParams.get('highlight_text');
|
||||
const startOffset = parseInt(urlParams.get('start_offset'));
|
||||
const endOffset = parseInt(urlParams.get('end_offset'));
|
||||
|
||||
if (highlightText && !isNaN(startOffset) && !isNaN(endOffset)) {
|
||||
console.log('🎯 URL에서 하이라이트 요청:', { highlightText, startOffset, endOffset });
|
||||
|
||||
// 임시 하이라이트 적용 및 스크롤
|
||||
setTimeout(() => {
|
||||
this.highlightAndScrollToText({
|
||||
targetText: highlightText,
|
||||
startOffset: startOffset,
|
||||
endOffset: endOffset
|
||||
});
|
||||
}, 500); // DOM 로딩 완료 후 실행
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 문서 내 스크립트 핸들러 설정
|
||||
*/
|
||||
setupDocumentScriptHandlers() {
|
||||
// 업로드된 HTML 문서에서 사용할 수 있는 전역 함수들 정의
|
||||
|
||||
// 언어 토글 함수 (많은 문서에서 사용)
|
||||
window.toggleLanguage = function() {
|
||||
const koreanContent = document.getElementById('korean-content');
|
||||
const englishContent = document.getElementById('english-content');
|
||||
|
||||
if (koreanContent && englishContent) {
|
||||
if (koreanContent.style.display === 'none') {
|
||||
koreanContent.style.display = 'block';
|
||||
englishContent.style.display = 'none';
|
||||
} else {
|
||||
koreanContent.style.display = 'none';
|
||||
englishContent.style.display = 'block';
|
||||
}
|
||||
} else {
|
||||
// 다른 언어 토글 방식들
|
||||
const elements = document.querySelectorAll('[data-lang]');
|
||||
elements.forEach(el => {
|
||||
if (el.dataset.lang === 'ko') {
|
||||
el.style.display = el.style.display === 'none' ? 'block' : 'none';
|
||||
} else if (el.dataset.lang === 'en') {
|
||||
el.style.display = el.style.display === 'none' ? 'block' : 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 문서 인쇄 함수
|
||||
window.printDocument = function() {
|
||||
// 현재 페이지의 헤더/푸터 숨기고 문서 내용만 인쇄
|
||||
const originalTitle = document.title;
|
||||
const printContent = document.getElementById('document-content');
|
||||
|
||||
if (printContent) {
|
||||
const printWindow = window.open('', '_blank');
|
||||
printWindow.document.write(`
|
||||
<html>
|
||||
<head>
|
||||
<title>${originalTitle}</title>
|
||||
<style>
|
||||
body { font-family: Arial, sans-serif; margin: 20px; }
|
||||
@media print { body { margin: 0; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>${printContent.innerHTML}</body>
|
||||
</html>
|
||||
`);
|
||||
printWindow.document.close();
|
||||
printWindow.print();
|
||||
} else {
|
||||
window.print();
|
||||
}
|
||||
};
|
||||
|
||||
// 링크 처리 함수 (문서 내 링크가 새 탭에서 열리지 않도록)
|
||||
document.addEventListener('click', function(e) {
|
||||
const link = e.target.closest('a');
|
||||
if (link && link.href && !link.href.startsWith('#')) {
|
||||
// 외부 링크는 새 탭에서 열기
|
||||
if (!link.href.includes(window.location.hostname)) {
|
||||
e.preventDefault();
|
||||
window.open(link.href, '_blank');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 텍스트 하이라이트 및 스크롤 (임시 하이라이트)
|
||||
* 이 함수는 나중에 HighlightManager로 이동될 예정
|
||||
*/
|
||||
highlightAndScrollToText({ targetText, startOffset, endOffset }) {
|
||||
// 임시 구현 - ViewerCore의 highlightAndScrollToText 호출
|
||||
if (window.documentViewerInstance && window.documentViewerInstance.highlightAndScrollToText) {
|
||||
window.documentViewerInstance.highlightAndScrollToText(targetText, startOffset, endOffset);
|
||||
} else {
|
||||
// 폴백: 간단한 스크롤만
|
||||
console.log('🎯 텍스트 하이라이트 요청 (폴백):', { targetText, startOffset, endOffset });
|
||||
|
||||
const documentContent = document.getElementById('document-content');
|
||||
if (!documentContent) return;
|
||||
|
||||
const textContent = documentContent.textContent;
|
||||
const targetIndex = textContent.indexOf(targetText);
|
||||
|
||||
if (targetIndex !== -1) {
|
||||
const scrollRatio = targetIndex / textContent.length;
|
||||
const scrollPosition = documentContent.scrollHeight * scrollRatio;
|
||||
|
||||
window.scrollTo({
|
||||
top: scrollPosition,
|
||||
behavior: 'smooth'
|
||||
});
|
||||
|
||||
console.log('✅ 텍스트로 스크롤 완료 (폴백)');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 전역으로 내보내기
|
||||
window.DocumentLoader = DocumentLoader;
|
||||
Reference in New Issue
Block a user