Fix: 하이라이트 메모 표시 오류 수정

- highlight-manager.js에서 showHighlightTooltip 함수 호출 시 배열 대신 단일 객체 전달하도록 수정
- 하이라이트 클릭 시 메모가 0개로 표시되던 문제 해결
- getOverlappingElements 함수에 디버깅 로그 추가
- 하이라이트 매니저 상태 확인 로그 추가
- 브라우저 캐시 무효화를 위한 버전 업데이트 (v=2025012617)
This commit is contained in:
Hyungi Ahn
2025-09-04 07:48:43 +09:00
parent 8f776a5281
commit 0786bdc86d
8 changed files with 1355 additions and 133 deletions

View File

@@ -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, '&quot;')}], 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, '&quot;')})">
<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, '&quot;')})">
<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);