Fix: 하이라이트 메모 표시 오류 수정
- highlight-manager.js에서 showHighlightTooltip 함수 호출 시 배열 대신 단일 객체 전달하도록 수정 - 하이라이트 클릭 시 메모가 0개로 표시되던 문제 해결 - getOverlappingElements 함수에 디버깅 로그 추가 - 하이라이트 매니저 상태 확인 로그 추가 - 브라우저 캐시 무효화를 위한 버전 업데이트 (v=2025012617)
This commit is contained in:
@@ -1136,6 +1136,423 @@ window.documentViewer = () => ({
|
||||
}
|
||||
},
|
||||
|
||||
// ==================== 통합 툴팁 처리 ====================
|
||||
/**
|
||||
* 클릭된 요소에서 링크, 백링크, 하이라이트 모두 찾기 (완전 개선 버전)
|
||||
*/
|
||||
getOverlappingElements(clickedElement) {
|
||||
const selectedText = clickedElement.textContent.trim();
|
||||
console.log('🔍 통합 요소 찾기 시작:', selectedText);
|
||||
console.log('🔍 하이라이트 매니저 상태:', {
|
||||
highlightManager: !!this.highlightManager,
|
||||
highlightsCount: this.highlightManager?.highlights?.length || 0,
|
||||
highlights: this.highlightManager?.highlights || []
|
||||
});
|
||||
|
||||
// 결과 배열들
|
||||
const overlappingLinks = [];
|
||||
const overlappingBacklinks = [];
|
||||
const overlappingHighlights = [];
|
||||
|
||||
// 1. 모든 링크 요소 찾기 (같은 텍스트)
|
||||
const allLinkElements = document.querySelectorAll('.document-link');
|
||||
allLinkElements.forEach(linkEl => {
|
||||
if (linkEl.textContent.trim() === selectedText) {
|
||||
const linkId = linkEl.dataset.linkId;
|
||||
const link = this.linkManager.documentLinks.find(l => l.id === linkId);
|
||||
if (link && !overlappingLinks.find(l => l.id === link.id)) {
|
||||
overlappingLinks.push(link);
|
||||
console.log('✅ 겹치는 링크 발견:', link.target_document_title);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 2. 모든 백링크 요소 찾기 (같은 텍스트)
|
||||
const allBacklinkElements = document.querySelectorAll('.backlink-highlight');
|
||||
allBacklinkElements.forEach(backlinkEl => {
|
||||
if (backlinkEl.textContent.trim() === selectedText) {
|
||||
const backlinkId = backlinkEl.dataset.backlinkId;
|
||||
const backlink = this.linkManager.backlinks.find(b => b.id === backlinkId);
|
||||
if (backlink && !overlappingBacklinks.find(b => b.id === backlink.id)) {
|
||||
overlappingBacklinks.push(backlink);
|
||||
console.log('✅ 겹치는 백링크 발견:', backlink.source_document_title);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 3. 모든 하이라이트 요소 찾기 (같은 텍스트)
|
||||
const allHighlightElements = document.querySelectorAll('.highlight-span');
|
||||
console.log('🔍 페이지의 모든 하이라이트 요소:', allHighlightElements.length, '개');
|
||||
allHighlightElements.forEach(highlightEl => {
|
||||
const highlightText = highlightEl.textContent.trim();
|
||||
|
||||
// 텍스트가 정확히 일치하거나 포함 관계인 경우
|
||||
if (highlightText === selectedText ||
|
||||
highlightText.includes(selectedText) ||
|
||||
selectedText.includes(highlightText)) {
|
||||
|
||||
const highlightId = highlightEl.dataset.highlightId;
|
||||
console.log('🔍 하이라이트 요소 확인:', {
|
||||
element: highlightEl,
|
||||
highlightId: highlightId,
|
||||
text: highlightText,
|
||||
selectedText: selectedText
|
||||
});
|
||||
|
||||
const highlight = this.highlightManager.highlights.find(h => h.id === highlightId);
|
||||
if (highlight && !overlappingHighlights.find(h => h.id === highlight.id)) {
|
||||
overlappingHighlights.push(highlight);
|
||||
console.log('✅ 겹치는 하이라이트 발견:', {
|
||||
id: highlight.id,
|
||||
text: highlightText,
|
||||
color: highlight.highlight_color
|
||||
});
|
||||
} else if (!highlight) {
|
||||
console.log('❌ 하이라이트 데이터를 찾을 수 없음:', highlightId);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
console.log('📊 최종 발견된 요소들:', {
|
||||
links: overlappingLinks.length,
|
||||
backlinks: overlappingBacklinks.length,
|
||||
highlights: overlappingHighlights.length,
|
||||
selectedText: selectedText
|
||||
});
|
||||
|
||||
return {
|
||||
links: overlappingLinks,
|
||||
backlinks: overlappingBacklinks,
|
||||
highlights: overlappingHighlights,
|
||||
selectedText: selectedText
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* 통합 툴팁 표시 (링크 + 하이라이트 + 백링크)
|
||||
*/
|
||||
async showUnifiedTooltip(overlappingElements, element) {
|
||||
const { links = [], highlights = [], backlinks = [], selectedText } = overlappingElements;
|
||||
|
||||
console.log('🎯 통합 툴팁 표시:', {
|
||||
links: links.length,
|
||||
highlights: highlights.length,
|
||||
backlinks: backlinks.length
|
||||
});
|
||||
|
||||
// 하이라이트가 있으면 메모 데이터 로드
|
||||
if (highlights.length > 0) {
|
||||
console.log('📝 통합 툴팁용 메모 로드 시작...');
|
||||
const documentId = this.documentId;
|
||||
const contentType = this.contentType;
|
||||
await this.highlightManager.loadNotes(documentId, contentType);
|
||||
console.log('📝 통합 툴팁용 메모 로드 완료:', this.highlightManager.notes.length, '개');
|
||||
}
|
||||
|
||||
// 기존 툴팁들 숨기기
|
||||
this.linkManager.hideTooltip();
|
||||
this.highlightManager.hideTooltip();
|
||||
|
||||
// 툴팁 컨테이너 생성
|
||||
const tooltip = document.createElement('div');
|
||||
tooltip.id = 'unified-tooltip';
|
||||
tooltip.className = 'fixed z-50 bg-white rounded-xl shadow-2xl border border-gray-200';
|
||||
tooltip.style.cssText = `
|
||||
background: linear-gradient(135deg, #ffffff 0%, #f8fafc 100%);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(148, 163, 184, 0.2);
|
||||
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||||
max-width: 90vw;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
z-index: 9999;
|
||||
`;
|
||||
|
||||
const totalElements = links.length + highlights.length + backlinks.length;
|
||||
|
||||
let tooltipHTML = `
|
||||
<div class="p-6">
|
||||
<div class="mb-4">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="text-lg font-semibold text-gray-800 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2 text-purple-600" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M3 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zm0 4a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z" clip-rule="evenodd"/>
|
||||
겹치는 요소들
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">${totalElements}개 요소</div>
|
||||
</div>
|
||||
|
||||
<div class="font-medium text-gray-900 bg-purple-50 px-4 py-3 rounded-lg border-l-4 border-purple-500">
|
||||
<div class="text-sm text-purple-700 mb-1">선택된 텍스트</div>
|
||||
<div class="text-base">"${selectedText}"</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// 하이라이트 섹션
|
||||
if (highlights.length > 0) {
|
||||
tooltipHTML += `
|
||||
<div class="mb-6">
|
||||
<div class="text-sm font-medium text-gray-700 mb-3 flex items-center">
|
||||
<svg class="w-4 h-4 mr-2 text-yellow-600" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"/>
|
||||
하이라이트 (${highlights.length}개)
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
`;
|
||||
|
||||
highlights.forEach(highlight => {
|
||||
const colorName = this.highlightManager.getColorName(highlight.highlight_color);
|
||||
const createdDate = this.formatDate(highlight.created_at);
|
||||
const notes = this.highlightManager.notes.filter(note => note.highlight_id === highlight.id);
|
||||
|
||||
console.log(`📝 통합 툴팁 - 하이라이트 ${highlight.id}의 메모:`, notes.length, '개');
|
||||
if (notes.length > 0) {
|
||||
console.log('📝 메모 내용:', notes.map(n => n.content));
|
||||
}
|
||||
|
||||
tooltipHTML += `
|
||||
<div class="border rounded-lg p-3 bg-gradient-to-r from-yellow-50 to-orange-50 cursor-pointer hover:shadow-md transition-shadow duration-200"
|
||||
onclick="window.documentViewerInstance.highlightManager.showHighlightTooltip([${JSON.stringify(highlight).replace(/"/g, '"')}], this.parentElement)">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="w-3 h-3 rounded-full" style="background-color: ${highlight.highlight_color}"></div>
|
||||
<span class="text-sm font-medium text-gray-800">${colorName}</span>
|
||||
<span class="text-xs text-gray-500">${createdDate}</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-600">${notes.length}개 메모</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
tooltipHTML += `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 링크 섹션
|
||||
if (links.length > 0) {
|
||||
tooltipHTML += `
|
||||
<div class="mb-4">
|
||||
<div class="text-sm font-medium text-gray-700 mb-3 flex items-center">
|
||||
<svg class="w-4 h-4 mr-2 text-purple-600" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
링크 (${links.length}개)
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
`;
|
||||
|
||||
links.forEach(link => {
|
||||
const isNote = link.target_content_type === 'note';
|
||||
const bgClass = isNote ? 'from-green-50 to-emerald-50' : 'from-purple-50 to-indigo-50';
|
||||
const iconClass = isNote ? 'text-green-600' : 'text-purple-600';
|
||||
const createdDate = this.formatDate(link.created_at);
|
||||
|
||||
tooltipHTML += `
|
||||
<div class="border rounded-lg p-3 bg-gradient-to-r ${bgClass} transition-colors duration-200 relative group">
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex-1 cursor-pointer" onclick="window.documentViewerInstance.navigateToLink(${JSON.stringify(link).replace(/"/g, '"')})">
|
||||
<div class="flex items-center mb-2">
|
||||
<svg class="w-4 h-4 mr-2 ${iconClass}" fill="currentColor" viewBox="0 0 20 20">
|
||||
${isNote ?
|
||||
'<path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"/><path fill-rule="evenodd" d="M4 5a2 2 0 012-2v1a1 1 0 102 0V3a2 2 0 012 0v1a1 1 0 102 0V3a2 2 0 012 2v6.586A2 2 0 0115.414 13L13 15.586A2 2 0 0111.586 16H6a2 2 0 01-2-2V5zm8 4a1 1 0 10-2 0v2a1 1 0 102 0V9z" clip-rule="evenodd"/>' :
|
||||
'<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"/>'
|
||||
}
|
||||
</svg>
|
||||
<span class="font-medium ${iconClass}">${link.target_document_title}</span>
|
||||
</div>
|
||||
|
||||
${link.target_text ? `
|
||||
<div class="mb-2 p-2 bg-gray-50 rounded border-l-3 ${isNote ? 'border-green-400' : 'border-purple-400'}">
|
||||
<div class="text-xs ${isNote ? 'text-green-700' : 'text-purple-700'} mb-1">연결된 텍스트</div>
|
||||
<div class="text-sm text-gray-800 font-medium">"${link.target_text}"</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${link.description ? `
|
||||
<div class="mb-2 p-2 bg-blue-50 rounded border-l-3 border-blue-400">
|
||||
<div class="text-xs text-blue-700 mb-1">링크 설명</div>
|
||||
<div class="text-sm text-blue-800">${link.description}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="text-xs text-gray-500 flex items-center justify-between">
|
||||
<span class="flex items-center">
|
||||
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M12.586 4.586a2 2 0 112.828 2.828l-3 3a2 2 0 01-2.828 0 1 1 0 00-1.414 1.414 4 4 0 005.656 0l3-3a4 4 0 00-5.656-5.656l-1.5 1.5a1 1 0 101.414 1.414l1.5-1.5zm-5 5a2 2 0 012.828 0 1 1 0 101.414-1.414 4 4 0 00-5.656 0l-3 3a4 4 0 105.656 5.656l1.5-1.5a1 1 0 10-1.414-1.414l-1.5 1.5a2 2 0 11-2.828-2.828l3-3z" clip-rule="evenodd"/>
|
||||
${link.link_type === 'text_fragment' ? '텍스트 조각 링크' : '문서 링크'}
|
||||
</span>
|
||||
<span>${createdDate}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 삭제 버튼 -->
|
||||
<button onclick="event.stopPropagation(); window.documentViewerInstance.deleteLinkWithConfirm('${link.id}', '${link.target_document_title.replace(/'/g, "\\'")}');"
|
||||
class="ml-3 p-1 text-gray-400 hover:text-red-600 hover:bg-red-50 rounded transition-colors duration-200 opacity-0 group-hover:opacity-100"
|
||||
title="링크 삭제">
|
||||
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
tooltipHTML += `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
// 백링크 섹션
|
||||
if (backlinks.length > 0) {
|
||||
tooltipHTML += `
|
||||
<div class="mb-4">
|
||||
<div class="text-sm font-medium text-gray-700 mb-3 flex items-center">
|
||||
<svg class="w-4 h-4 mr-2 text-orange-600" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M7.707 3.293a1 1 0 010 1.414L5.414 7H11a7 7 0 017 7v2a1 1 0 11-2 0v-2a5 5 0 00-5-5H5.414l2.293 2.293a1 1 0 11-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
||||
</svg>
|
||||
백링크 (${backlinks.length}개)
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
`;
|
||||
|
||||
backlinks.forEach(backlink => {
|
||||
const createdDate = this.formatDate(backlink.created_at);
|
||||
|
||||
tooltipHTML += `
|
||||
<div class="border rounded-lg p-3 bg-gradient-to-r from-orange-50 to-red-50 transition-colors duration-200 relative group">
|
||||
<div class="cursor-pointer" onclick="window.documentViewerInstance.navigateToBacklink(${JSON.stringify(backlink).replace(/"/g, '"')})">
|
||||
<div class="flex items-center mb-2">
|
||||
<svg class="w-4 h-4 mr-2 text-orange-600" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clip-rule="evenodd"/>
|
||||
<span class="font-medium text-orange-600">${backlink.source_document_title}</span>
|
||||
</div>
|
||||
|
||||
<div class="mb-2 p-2 bg-white rounded border-l-3 border-orange-400">
|
||||
<div class="text-xs text-orange-600 mb-1">원본 문서에서 링크로 설정한 텍스트</div>
|
||||
<div class="text-sm text-gray-800 font-medium">"${backlink.selected_text}"</div>
|
||||
</div>
|
||||
|
||||
${backlink.target_text ? `
|
||||
<div class="mb-2 p-2 bg-white rounded border-l-3 border-blue-400">
|
||||
<div class="text-xs text-blue-600 mb-1">현재 문서에서 연결된 구체적인 텍스트</div>
|
||||
<div class="text-sm text-blue-800 font-medium">"${backlink.target_text}"</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
${backlink.description ? `
|
||||
<div class="mb-2 p-2 bg-white rounded border-l-3 border-green-400">
|
||||
<div class="text-xs text-green-600 mb-1">링크 설명</div>
|
||||
<div class="text-sm text-green-800">${backlink.description}</div>
|
||||
</div>
|
||||
` : ''}
|
||||
|
||||
<div class="text-xs text-gray-500 flex items-center justify-between">
|
||||
<span class="flex items-center">
|
||||
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M7.707 3.293a1 1 0 010 1.414L5.414 7H11a7 7 0 017 7v2a1 1 0 11-2 0v-2a5 5 0 00-5-5H5.414l2.293 2.293a1 1 0 11-1.414 1.414l-4-4a1 1 0 010-1.414l4-4a1 1 0 011.414 0z" clip-rule="evenodd"/>
|
||||
백링크
|
||||
</span>
|
||||
<span>${createdDate}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
|
||||
tooltipHTML += `
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
tooltipHTML += `
|
||||
<div class="flex justify-end pt-4 border-t border-gray-200">
|
||||
<button onclick="window.documentViewerInstance.hideUnifiedTooltip()"
|
||||
class="text-xs bg-gray-500 text-white px-3 py-1 rounded hover:bg-gray-600 transition-colors">
|
||||
닫기
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
tooltip.innerHTML = tooltipHTML;
|
||||
document.body.appendChild(tooltip);
|
||||
|
||||
// 위치 조정
|
||||
this.positionTooltip(tooltip, element);
|
||||
},
|
||||
|
||||
/**
|
||||
* 통합 툴팁 숨기기
|
||||
*/
|
||||
hideUnifiedTooltip() {
|
||||
const tooltip = document.getElementById('unified-tooltip');
|
||||
if (tooltip) {
|
||||
tooltip.remove();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 툴팁 위치 조정 (화면 밖으로 나가지 않도록 개선)
|
||||
*/
|
||||
positionTooltip(tooltip, element) {
|
||||
const rect = element.getBoundingClientRect();
|
||||
const tooltipRect = tooltip.getBoundingClientRect();
|
||||
const viewportWidth = window.innerWidth;
|
||||
const viewportHeight = window.innerHeight;
|
||||
const scrollX = window.scrollX;
|
||||
const scrollY = window.scrollY;
|
||||
|
||||
console.log('🎯 툴팁 위치 계산:', {
|
||||
elementRect: rect,
|
||||
tooltipSize: { width: tooltipRect.width, height: tooltipRect.height },
|
||||
viewport: { width: viewportWidth, height: viewportHeight }
|
||||
});
|
||||
|
||||
// 기본 위치: 요소 아래 중앙
|
||||
let left = rect.left + scrollX + (rect.width / 2) - (tooltipRect.width / 2);
|
||||
let top = rect.bottom + scrollY + 10;
|
||||
|
||||
// 좌우 경계 체크 및 조정
|
||||
const margin = 20;
|
||||
if (left < margin) {
|
||||
left = margin;
|
||||
console.log('🔧 좌측 경계 조정:', left);
|
||||
} else if (left + tooltipRect.width > viewportWidth - margin) {
|
||||
left = viewportWidth - tooltipRect.width - margin;
|
||||
console.log('🔧 우측 경계 조정:', left);
|
||||
}
|
||||
|
||||
// 상하 경계 체크 및 조정
|
||||
if (top + tooltipRect.height > viewportHeight - margin) {
|
||||
// 요소 위쪽에 표시
|
||||
top = rect.top + scrollY - tooltipRect.height - 10;
|
||||
console.log('🔧 상단으로 이동:', top);
|
||||
|
||||
// 위쪽에도 공간이 부족하면 뷰포트 내에 강제로 맞춤
|
||||
if (top < margin) {
|
||||
top = margin;
|
||||
console.log('🔧 상단 경계 조정:', top);
|
||||
}
|
||||
}
|
||||
|
||||
// 최종 위치 설정
|
||||
tooltip.style.position = 'fixed';
|
||||
tooltip.style.left = `${left - scrollX}px`;
|
||||
tooltip.style.top = `${top - scrollY}px`;
|
||||
|
||||
console.log('✅ 최종 툴팁 위치:', {
|
||||
left: left - scrollX,
|
||||
top: top - scrollY
|
||||
});
|
||||
},
|
||||
|
||||
// ==================== 유틸리티 메서드 ====================
|
||||
formatDate(dateString) {
|
||||
return new Date(dateString).toLocaleString('ko-KR');
|
||||
@@ -1145,20 +1562,6 @@ window.documentViewer = () => ({
|
||||
return new Date(dateString).toLocaleDateString('ko-KR');
|
||||
},
|
||||
|
||||
getColorName(color) {
|
||||
const colorNames = {
|
||||
'#FFFF00': '노란색',
|
||||
'#00FF00': '초록색',
|
||||
'#FF0000': '빨간색',
|
||||
'#0000FF': '파란색',
|
||||
'#FF00FF': '보라색',
|
||||
'#00FFFF': '청록색',
|
||||
'#FFA500': '주황색',
|
||||
'#FFC0CB': '분홍색'
|
||||
};
|
||||
return colorNames[color] || '기타';
|
||||
},
|
||||
|
||||
getSelectedBookTitle() {
|
||||
const selectedBook = this.availableBooks.find(book => book.id === this.linkForm.target_book_id);
|
||||
return selectedBook ? selectedBook.title : '서적을 선택하세요';
|
||||
@@ -1260,6 +1663,230 @@ window.documentViewer = () => ({
|
||||
return this.linkManager.navigateToSourceDocument(backlink.source_document_id, backlink);
|
||||
},
|
||||
|
||||
// 링크 삭제 (확인 후)
|
||||
async deleteLinkWithConfirm(linkId, targetTitle) {
|
||||
console.log('🗑️ 링크 삭제 요청:', { linkId, targetTitle });
|
||||
|
||||
const confirmed = confirm(`"${targetTitle}"로의 링크를 삭제하시겠습니까?`);
|
||||
if (!confirmed) {
|
||||
console.log('❌ 링크 삭제 취소됨');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('🗑️ 링크 삭제 시작:', linkId);
|
||||
|
||||
// API 호출
|
||||
await this.api.deleteDocumentLink(linkId);
|
||||
console.log('✅ 링크 삭제 성공');
|
||||
|
||||
// 툴팁 숨기기
|
||||
this.linkManager.hideTooltip();
|
||||
|
||||
// 캐시 무효화
|
||||
console.log('🗑️ 링크 캐시 무효화 시작...');
|
||||
if (window.cachedApi && window.cachedApi.invalidateRelatedCache) {
|
||||
if (this.contentType === 'note') {
|
||||
window.cachedApi.invalidateRelatedCache(`/note-documents/${this.documentId}/links`, ['links']);
|
||||
} else {
|
||||
window.cachedApi.invalidateRelatedCache(`/documents/${this.documentId}/links`, ['links']);
|
||||
}
|
||||
console.log('✅ 링크 캐시 무효화 완료');
|
||||
}
|
||||
|
||||
// 링크 목록 새로고침
|
||||
console.log('🔄 링크 목록 새로고침 시작...');
|
||||
await this.linkManager.loadDocumentLinks(this.documentId, this.contentType);
|
||||
this.documentLinks = this.linkManager.documentLinks || [];
|
||||
console.log('📊 새로고침된 링크 개수:', this.documentLinks.length);
|
||||
|
||||
// 링크 렌더링
|
||||
console.log('🎨 링크 렌더링 시작...');
|
||||
this.linkManager.renderDocumentLinks();
|
||||
console.log('✅ 링크 렌더링 완료');
|
||||
|
||||
// 백링크도 다시 로드 (삭제된 링크가 다른 문서의 백링크였을 수 있음)
|
||||
console.log('🔄 백링크 새로고침 시작...');
|
||||
if (window.cachedApi && window.cachedApi.invalidateRelatedCache) {
|
||||
if (this.contentType === 'note') {
|
||||
window.cachedApi.invalidateRelatedCache(`/note-documents/${this.documentId}/backlinks`, ['links']);
|
||||
} else {
|
||||
window.cachedApi.invalidateRelatedCache(`/documents/${this.documentId}/backlinks`, ['links']);
|
||||
}
|
||||
console.log('✅ 백링크 캐시도 무효화 완료');
|
||||
}
|
||||
await this.linkManager.loadBacklinks(this.documentId, this.contentType);
|
||||
this.backlinks = this.linkManager.backlinks || [];
|
||||
this.linkManager.renderBacklinks();
|
||||
console.log('✅ 백링크 새로고침 완료');
|
||||
|
||||
// 성공 메시지
|
||||
this.showSuccessMessage('링크가 삭제되었습니다.');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 링크 삭제 실패:', error);
|
||||
alert('링크 삭제에 실패했습니다: ' + error.message);
|
||||
}
|
||||
},
|
||||
|
||||
// 성공 메시지 표시
|
||||
showSuccessMessage(message) {
|
||||
const toast = document.createElement('div');
|
||||
toast.className = 'fixed top-4 right-4 bg-green-500 text-white px-4 py-2 rounded-lg shadow-lg z-50 transition-opacity duration-300';
|
||||
toast.textContent = message;
|
||||
|
||||
document.body.appendChild(toast);
|
||||
|
||||
setTimeout(() => {
|
||||
toast.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
if (toast.parentNode) {
|
||||
toast.parentNode.removeChild(toast);
|
||||
}
|
||||
}, 300);
|
||||
}, 2000);
|
||||
},
|
||||
|
||||
// 하이라이트 관련 추가 기능들
|
||||
async changeHighlightColor(highlightId) {
|
||||
console.log('🎨 하이라이트 색상 변경:', highlightId);
|
||||
|
||||
const colors = [
|
||||
{ name: '노란색', value: '#FFFF00' },
|
||||
{ name: '초록색', value: '#00FF00' },
|
||||
{ name: '파란색', value: '#00BFFF' },
|
||||
{ name: '분홍색', value: '#FFB6C1' },
|
||||
{ name: '주황색', value: '#FFA500' },
|
||||
{ name: '보라색', value: '#DDA0DD' }
|
||||
];
|
||||
|
||||
const colorOptions = colors.map(c => `${c.name} (${c.value})`).join('\n');
|
||||
const selectedColor = prompt(`새로운 색상을 선택하세요:\n\n${colorOptions}\n\n색상 코드를 입력하세요 (예: #FFFF00):`);
|
||||
|
||||
if (selectedColor && selectedColor.match(/^#[0-9A-Fa-f]{6}$/)) {
|
||||
try {
|
||||
await this.highlightManager.updateHighlightColor(highlightId, selectedColor);
|
||||
this.showSuccessMessage('하이라이트 색상이 변경되었습니다.');
|
||||
} catch (error) {
|
||||
console.error('❌ 색상 변경 실패:', error);
|
||||
alert('색상 변경에 실패했습니다: ' + error.message);
|
||||
}
|
||||
} else if (selectedColor !== null) {
|
||||
alert('올바른 색상 코드를 입력해주세요 (예: #FFFF00)');
|
||||
}
|
||||
},
|
||||
|
||||
async duplicateHighlight(highlightId) {
|
||||
console.log('📋 하이라이트 복사:', highlightId);
|
||||
|
||||
try {
|
||||
await this.highlightManager.duplicateHighlight(highlightId);
|
||||
this.showSuccessMessage('하이라이트가 복사되었습니다.');
|
||||
} catch (error) {
|
||||
console.error('❌ 하이라이트 복사 실패:', error);
|
||||
alert('하이라이트 복사에 실패했습니다: ' + error.message);
|
||||
}
|
||||
},
|
||||
|
||||
async deleteHighlightWithConfirm(highlightId) {
|
||||
console.log('🗑️ 하이라이트 삭제 확인:', highlightId);
|
||||
|
||||
const confirmed = confirm('이 하이라이트를 삭제하시겠습니까?\n\n⚠️ 주의: 연결된 모든 메모도 함께 삭제됩니다.');
|
||||
if (!confirmed) {
|
||||
console.log('❌ 하이라이트 삭제 취소됨');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await this.highlightManager.deleteHighlight(highlightId);
|
||||
this.highlightManager.hideTooltip();
|
||||
this.showSuccessMessage('하이라이트가 삭제되었습니다.');
|
||||
} catch (error) {
|
||||
console.error('❌ 하이라이트 삭제 실패:', error);
|
||||
alert('하이라이트 삭제에 실패했습니다: ' + error.message);
|
||||
}
|
||||
},
|
||||
|
||||
async editNote(noteId, currentContent) {
|
||||
console.log('✏️ 메모 편집:', noteId);
|
||||
console.log('🔍 HighlightManager 상태:', this.highlightManager);
|
||||
console.log('🔍 updateNote 함수 존재:', typeof this.highlightManager?.updateNote);
|
||||
|
||||
if (!this.highlightManager) {
|
||||
console.error('❌ HighlightManager가 초기화되지 않음');
|
||||
alert('하이라이트 매니저가 초기화되지 않았습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof this.highlightManager.updateNote !== 'function') {
|
||||
console.error('❌ updateNote 함수가 존재하지 않음');
|
||||
alert('메모 업데이트 함수가 존재하지 않습니다.');
|
||||
return;
|
||||
}
|
||||
|
||||
const newContent = prompt('메모 내용을 수정하세요:', currentContent);
|
||||
if (newContent !== null && newContent.trim() !== currentContent) {
|
||||
try {
|
||||
await this.highlightManager.updateNote(noteId, newContent.trim());
|
||||
this.showSuccessMessage('메모가 수정되었습니다.');
|
||||
} catch (error) {
|
||||
console.error('❌ 메모 수정 실패:', error);
|
||||
alert('메모 수정에 실패했습니다: ' + error.message);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// 백링크 삭제 (확인 후)
|
||||
async deleteBacklinkWithConfirm(backlinkId, sourceTitle) {
|
||||
console.log('🗑️ 백링크 삭제 요청:', { backlinkId, sourceTitle });
|
||||
|
||||
const confirmed = confirm(`"${sourceTitle}"에서 오는 백링크를 삭제하시겠습니까?\n\n⚠️ 주의: 이는 원본 문서의 링크를 삭제합니다.`);
|
||||
if (!confirmed) {
|
||||
console.log('❌ 백링크 삭제 취소됨');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('🗑️ 백링크 삭제 시작:', backlinkId);
|
||||
|
||||
// 백링크 삭제는 실제로는 원본 링크를 삭제하는 것
|
||||
await this.api.deleteDocumentLink(backlinkId);
|
||||
console.log('✅ 백링크 삭제 성공');
|
||||
|
||||
// 툴팁 숨기기
|
||||
this.linkManager.hideTooltip();
|
||||
|
||||
// 캐시 무효화 (현재 문서의 백링크 캐시)
|
||||
console.log('🗑️ 백링크 캐시 무효화 시작...');
|
||||
if (window.cachedApi && window.cachedApi.invalidateRelatedCache) {
|
||||
if (this.contentType === 'note') {
|
||||
window.cachedApi.invalidateRelatedCache(`/note-documents/${this.documentId}/backlinks`, ['links']);
|
||||
} else {
|
||||
window.cachedApi.invalidateRelatedCache(`/documents/${this.documentId}/backlinks`, ['links']);
|
||||
}
|
||||
console.log('✅ 백링크 캐시 무효화 완료');
|
||||
}
|
||||
|
||||
// 백링크 목록 새로고침
|
||||
console.log('🔄 백링크 목록 새로고침 시작...');
|
||||
await this.linkManager.loadBacklinks(this.documentId, this.contentType);
|
||||
this.backlinks = this.linkManager.backlinks || [];
|
||||
console.log('📊 새로고침된 백링크 개수:', this.backlinks.length);
|
||||
|
||||
// 백링크 렌더링
|
||||
console.log('🎨 백링크 렌더링 시작...');
|
||||
this.linkManager.renderBacklinks();
|
||||
console.log('✅ 백링크 렌더링 완료');
|
||||
|
||||
// 성공 메시지
|
||||
this.showSuccessMessage('백링크가 삭제되었습니다.');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 백링크 삭제 실패:', error);
|
||||
alert('백링크 삭제에 실패했습니다: ' + error.message);
|
||||
}
|
||||
},
|
||||
|
||||
// 북마크 관련
|
||||
scrollToBookmark(bookmark) {
|
||||
return this.bookmarkManager.scrollToBookmark(bookmark);
|
||||
|
||||
Reference in New Issue
Block a user