Fix: 하이라이트 메모 표시 오류 수정
- highlight-manager.js에서 showHighlightTooltip 함수 호출 시 배열 대신 단일 객체 전달하도록 수정 - 하이라이트 클릭 시 메모가 0개로 표시되던 문제 해결 - getOverlappingElements 함수에 디버깅 로그 추가 - 하이라이트 매니저 상태 확인 로그 추가 - 브라우저 캐시 무효화를 위한 버전 업데이트 (v=2025012617)
This commit is contained in:
@@ -42,14 +42,20 @@ class HighlightManager {
|
||||
*/
|
||||
async loadNotes(documentId, contentType) {
|
||||
try {
|
||||
console.log('📝 메모 로드 시작:', { documentId, contentType });
|
||||
|
||||
if (contentType === 'note') {
|
||||
this.notes = await this.api.get(`/note/${documentId}/notes`).catch(() => []);
|
||||
// 노트 문서의 하이라이트 메모
|
||||
this.notes = await this.api.get(`/highlight-notes/`, { note_document_id: documentId }).catch(() => []);
|
||||
} else {
|
||||
this.notes = await this.cachedApi.get('/notes', { document_id: documentId, content_type: contentType }, { category: 'notes' }).catch(() => []);
|
||||
// 일반 문서의 하이라이트 메모
|
||||
this.notes = await this.api.get(`/highlight-notes/`, { document_id: documentId }).catch(() => []);
|
||||
}
|
||||
|
||||
console.log('📝 메모 로드 완료:', this.notes.length, '개');
|
||||
return this.notes || [];
|
||||
} catch (error) {
|
||||
console.error('메모 로드 실패:', error);
|
||||
console.error('❌ 메모 로드 실패:', error);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
@@ -235,22 +241,46 @@ class HighlightManager {
|
||||
span.style.cursor = 'help';
|
||||
}
|
||||
|
||||
// 하이라이트 클릭 이벤트 추가 (겹치는 요소 감지)
|
||||
// 하이라이트 클릭 이벤트 추가 (통합 툴팁 사용)
|
||||
span.style.cursor = 'pointer';
|
||||
span.addEventListener('click', (e) => {
|
||||
span.addEventListener('click', async (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// 겹치는 링크나 백링크가 있는지 확인
|
||||
if (window.documentViewerInstance && window.documentViewerInstance.linkManager) {
|
||||
const overlappingElements = window.documentViewerInstance.linkManager.findOverlappingElements(span);
|
||||
if (overlappingElements.length > 0) {
|
||||
window.documentViewerInstance.linkManager.showOverlapMenu(e, span, overlappingElements, 'highlight');
|
||||
return;
|
||||
console.log('🎨 하이라이트 클릭됨:', {
|
||||
text: span.textContent,
|
||||
highlightId: span.dataset.highlightId,
|
||||
classList: Array.from(span.classList)
|
||||
});
|
||||
|
||||
// 링크, 백링크, 하이라이트 모두 찾기
|
||||
const overlappingElements = window.documentViewerInstance.getOverlappingElements(span);
|
||||
const totalElements = overlappingElements.links.length + overlappingElements.backlinks.length + overlappingElements.highlights.length;
|
||||
|
||||
console.log('🎨 하이라이트 클릭 분석:', {
|
||||
links: overlappingElements.links.length,
|
||||
backlinks: overlappingElements.backlinks.length,
|
||||
highlights: overlappingElements.highlights.length,
|
||||
total: totalElements,
|
||||
selectedText: overlappingElements.selectedText
|
||||
});
|
||||
|
||||
if (totalElements > 1) {
|
||||
// 통합 툴팁 표시 (링크 + 백링크 + 하이라이트)
|
||||
console.log('🎯 통합 툴팁 표시 시작 (하이라이트에서)');
|
||||
await window.documentViewerInstance.showUnifiedTooltip(overlappingElements, span);
|
||||
} else {
|
||||
// 단일 하이라이트 툴팁
|
||||
console.log('🎨 단일 하이라이트 툴팁 표시');
|
||||
// 클릭된 하이라이트 찾기
|
||||
const clickedHighlightId = span.dataset.highlightId;
|
||||
const clickedHighlight = this.highlights.find(h => h.id === clickedHighlightId);
|
||||
if (clickedHighlight) {
|
||||
await this.showHighlightTooltip(clickedHighlight, span);
|
||||
} else {
|
||||
console.error('❌ 클릭된 하이라이트를 찾을 수 없음:', clickedHighlightId);
|
||||
}
|
||||
}
|
||||
|
||||
this.showHighlightModal(highlights);
|
||||
});
|
||||
|
||||
// DOM 교체
|
||||
@@ -498,6 +528,109 @@ class HighlightManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 하이라이트 색상 변경
|
||||
*/
|
||||
async updateHighlightColor(highlightId, newColor) {
|
||||
try {
|
||||
console.log('🎨 하이라이트 색상 업데이트:', highlightId, newColor);
|
||||
|
||||
// API 호출 (구현 필요)
|
||||
await this.api.updateHighlight(highlightId, { highlight_color: newColor });
|
||||
|
||||
// 로컬 데이터 업데이트
|
||||
const highlight = this.highlights.find(h => h.id === highlightId);
|
||||
if (highlight) {
|
||||
highlight.highlight_color = newColor;
|
||||
}
|
||||
|
||||
// 하이라이트 다시 렌더링
|
||||
this.renderHighlights();
|
||||
this.hideTooltip();
|
||||
|
||||
console.log('✅ 하이라이트 색상 변경 완료');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 하이라이트 색상 변경 실패:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 하이라이트 복사
|
||||
*/
|
||||
async duplicateHighlight(highlightId) {
|
||||
try {
|
||||
console.log('📋 하이라이트 복사:', highlightId);
|
||||
|
||||
const originalHighlight = this.highlights.find(h => h.id === highlightId);
|
||||
if (!originalHighlight) {
|
||||
throw new Error('원본 하이라이트를 찾을 수 없습니다.');
|
||||
}
|
||||
|
||||
// 새 하이라이트 데이터 생성 (약간 다른 위치에)
|
||||
const duplicateData = {
|
||||
document_id: originalHighlight.document_id,
|
||||
start_offset: originalHighlight.start_offset,
|
||||
end_offset: originalHighlight.end_offset,
|
||||
selected_text: originalHighlight.selected_text,
|
||||
highlight_color: originalHighlight.highlight_color,
|
||||
highlight_type: originalHighlight.highlight_type
|
||||
};
|
||||
|
||||
// API 호출
|
||||
const newHighlight = await this.api.createHighlight(duplicateData);
|
||||
|
||||
// 로컬 데이터에 추가
|
||||
this.highlights.push(newHighlight);
|
||||
|
||||
// 하이라이트 다시 렌더링
|
||||
this.renderHighlights();
|
||||
this.hideTooltip();
|
||||
|
||||
console.log('✅ 하이라이트 복사 완료:', newHighlight);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 하이라이트 복사 실패:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 메모 업데이트
|
||||
*/
|
||||
async updateNote(noteId, newContent) {
|
||||
try {
|
||||
console.log('✏️ 메모 업데이트:', noteId, newContent);
|
||||
|
||||
// API 호출
|
||||
const apiToUse = this.cachedApi || this.api;
|
||||
await apiToUse.updateNote(noteId, { content: newContent });
|
||||
|
||||
// 로컬 데이터 업데이트
|
||||
const note = this.notes.find(n => n.id === noteId);
|
||||
if (note) {
|
||||
note.content = newContent;
|
||||
}
|
||||
|
||||
// 툴팁 새로고침 (현재 표시 중인 경우)
|
||||
const tooltip = document.getElementById('highlight-tooltip');
|
||||
if (tooltip) {
|
||||
// 간단히 툴팁을 다시 로드하는 대신 텍스트만 업데이트
|
||||
const noteElement = document.querySelector(`[data-note-id="${noteId}"] .text-gray-800`);
|
||||
if (noteElement) {
|
||||
noteElement.textContent = newContent;
|
||||
}
|
||||
}
|
||||
|
||||
console.log('✅ 메모 업데이트 완료');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ 메모 업데이트 실패:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 텍스트 오프셋 계산
|
||||
*/
|
||||
@@ -729,13 +862,70 @@ class HighlightManager {
|
||||
return colorNames[color] || '기타';
|
||||
}
|
||||
|
||||
/**
|
||||
* 날짜 포맷팅 (상세)
|
||||
*/
|
||||
formatDate(dateString) {
|
||||
if (!dateString) return '알 수 없음';
|
||||
const date = new Date(dateString);
|
||||
return date.toLocaleDateString('ko-KR', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 날짜 포맷팅 (간단)
|
||||
*/
|
||||
formatShortDate(dateString) {
|
||||
if (!dateString) return '';
|
||||
const date = new Date(dateString);
|
||||
const now = new Date();
|
||||
const diffTime = Math.abs(now - date);
|
||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
|
||||
|
||||
if (diffDays === 1) {
|
||||
return '오늘';
|
||||
} else if (diffDays === 2) {
|
||||
return '어제';
|
||||
} else if (diffDays <= 7) {
|
||||
return `${diffDays - 1}일 전`;
|
||||
} else {
|
||||
return date.toLocaleDateString('ko-KR', {
|
||||
month: 'short',
|
||||
day: 'numeric'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 하이라이트 툴팁 표시
|
||||
*/
|
||||
showHighlightTooltip(clickedHighlight, element) {
|
||||
async showHighlightTooltip(clickedHighlight, element) {
|
||||
// 기존 말풍선 제거
|
||||
this.hideTooltip();
|
||||
|
||||
// 메모 데이터 다시 로드 (최신 상태 보장)
|
||||
console.log('📝 하이라이트 툴팁용 메모 로드 시작...');
|
||||
const documentId = window.documentViewerInstance.documentId;
|
||||
const contentType = window.documentViewerInstance.contentType;
|
||||
|
||||
console.log('📝 메모 로드 파라미터:', { documentId, contentType });
|
||||
console.log('📝 기존 메모 개수:', this.notes ? this.notes.length : 'undefined');
|
||||
|
||||
await this.loadNotes(documentId, contentType);
|
||||
|
||||
console.log('📝 메모 로드 완료:', this.notes.length, '개');
|
||||
console.log('📝 로드된 메모 상세:', this.notes.map(n => ({
|
||||
id: n.id,
|
||||
highlight_id: n.highlight_id,
|
||||
content: n.content,
|
||||
created_at: n.created_at
|
||||
})));
|
||||
|
||||
// 동일한 범위의 모든 하이라이트 찾기
|
||||
const overlappingHighlights = this.findOverlappingHighlights(clickedHighlight);
|
||||
const colorGroups = this.groupHighlightsByColor(overlappingHighlights);
|
||||
@@ -754,9 +944,19 @@ class HighlightManager {
|
||||
|
||||
let tooltipHTML = `
|
||||
<div class="mb-4">
|
||||
<div class="text-sm text-gray-600 mb-2">선택된 텍스트</div>
|
||||
<div class="font-medium text-gray-900 bg-gray-100 px-3 py-2 rounded border-l-4 border-blue-500">
|
||||
"${longestText}"
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="text-lg font-semibold text-blue-800 flex items-center">
|
||||
<svg class="w-5 h-5 mr-2" 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"></path>
|
||||
</svg>
|
||||
하이라이트 정보
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">${overlappingHighlights.length}개 하이라이트</div>
|
||||
</div>
|
||||
|
||||
<div class="font-medium text-gray-900 bg-blue-50 px-4 py-3 rounded-lg border-l-4 border-blue-500">
|
||||
<div class="text-sm text-blue-700 mb-1">선택된 텍스트</div>
|
||||
<div class="text-base">"${longestText}"</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -766,39 +966,84 @@ class HighlightManager {
|
||||
|
||||
Object.entries(colorGroups).forEach(([color, highlights]) => {
|
||||
const colorName = this.getColorName(color);
|
||||
const allNotes = highlights.flatMap(h =>
|
||||
this.notes.filter(note => note.highlight_id === h.id)
|
||||
);
|
||||
|
||||
// 각 하이라이트에 대한 메모 찾기 (디버깅 로그 추가)
|
||||
const allNotes = highlights.flatMap(h => {
|
||||
const notesForHighlight = this.notes.filter(note => note.highlight_id === h.id);
|
||||
console.log(`📝 하이라이트 ${h.id}에 대한 메모:`, notesForHighlight.length, '개');
|
||||
if (notesForHighlight.length > 0) {
|
||||
console.log('📝 메모 내용:', notesForHighlight.map(n => n.content));
|
||||
}
|
||||
return notesForHighlight;
|
||||
});
|
||||
|
||||
console.log(`🎨 ${colorName} 하이라이트의 총 메모:`, allNotes.length, '개');
|
||||
|
||||
const createdDate = highlights[0].created_at ? this.formatDate(highlights[0].created_at) : '알 수 없음';
|
||||
|
||||
tooltipHTML += `
|
||||
<div class="border rounded-lg p-3" style="border-left: 4px solid ${color}">
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<div class="flex items-center space-x-2">
|
||||
<div class="w-3 h-3 rounded" style="background-color: ${color}"></div>
|
||||
<span class="text-sm font-medium text-gray-700">${colorName} 메모 (${allNotes.length})</span>
|
||||
<div class="border rounded-lg p-4 bg-gradient-to-r from-gray-50 to-gray-100" style="border-left: 4px solid ${color}">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div class="flex items-center space-x-3">
|
||||
<div class="w-4 h-4 rounded-full shadow-sm" style="background-color: ${color}"></div>
|
||||
<div>
|
||||
<span class="text-sm font-semibold text-gray-800">${colorName} 하이라이트</span>
|
||||
<div class="text-xs text-gray-600">${createdDate} 생성</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<button onclick="window.documentViewerInstance.highlightManager.changeHighlightColor('${highlights[0].id}')"
|
||||
class="text-xs bg-gray-500 text-white px-2 py-1 rounded hover:bg-gray-600 transition-colors"
|
||||
title="색상 변경">
|
||||
🎨
|
||||
</button>
|
||||
<button onclick="window.documentViewerInstance.highlightManager.showAddNoteForm('${highlights[0].id}')"
|
||||
class="text-xs bg-blue-500 text-white px-2 py-1 rounded hover:bg-blue-600 transition-colors">
|
||||
📝 메모 추가
|
||||
</button>
|
||||
</div>
|
||||
<button onclick="window.documentViewerInstance.highlightManager.showAddNoteForm('${highlights[0].id}')"
|
||||
class="text-xs bg-blue-500 text-white px-2 py-1 rounded hover:bg-blue-600">
|
||||
+ 추가
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="notes-list-${highlights[0].id}" class="space-y-2 max-h-32 overflow-y-auto">
|
||||
${allNotes.length > 0 ?
|
||||
allNotes.map(note => `
|
||||
<div class="bg-gray-50 p-2 rounded text-sm">
|
||||
<div class="text-gray-800">${note.content}</div>
|
||||
<div class="text-xs text-gray-500 mt-1 flex justify-between items-center">
|
||||
<span>${this.formatShortDate(note.created_at)} · Administrator</span>
|
||||
<button onclick="window.documentViewerInstance.highlightManager.deleteNote('${note.id}')"
|
||||
class="text-red-600 hover:text-red-800">
|
||||
삭제
|
||||
</button>
|
||||
<!-- 메모 목록 -->
|
||||
<div class="mb-3">
|
||||
<div class="text-sm font-medium text-gray-700 mb-2 flex items-center">
|
||||
<svg class="w-4 h-4 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M9 2a1 1 0 000 2h2a1 1 0 100-2H9z"></path>
|
||||
<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>
|
||||
</svg>
|
||||
메모 (${allNotes.length}개)
|
||||
</div>
|
||||
|
||||
<div id="notes-list-${highlights[0].id}" class="space-y-2 max-h-40 overflow-y-auto">
|
||||
${allNotes.length > 0 ?
|
||||
allNotes.map(note => `
|
||||
<div class="bg-white p-3 rounded-lg border shadow-sm group">
|
||||
<div class="text-gray-800 text-sm leading-relaxed">${note.content}</div>
|
||||
<div class="text-xs text-gray-500 mt-2 flex justify-between items-center">
|
||||
<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="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
${this.formatShortDate(note.created_at)}
|
||||
</span>
|
||||
<div class="opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<button onclick="window.documentViewerInstance.highlightManager.editNote('${note.id}', '${note.content.replace(/'/g, "\\'")}');"
|
||||
class="text-blue-600 hover:text-blue-800 mr-2 text-xs"
|
||||
title="메모 편집">
|
||||
✏️
|
||||
</button>
|
||||
<button onclick="window.documentViewerInstance.highlightManager.deleteNote('${note.id}')"
|
||||
class="text-red-600 hover:text-red-800 text-xs"
|
||||
title="메모 삭제">
|
||||
🗑️
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`).join('') :
|
||||
'<div class="text-sm text-gray-500 italic">메모가 없습니다</div>'
|
||||
}
|
||||
`).join('') :
|
||||
'<div class="text-sm text-gray-500 italic bg-white p-3 rounded-lg border">메모가 없습니다. 위의 "📝 메모 추가" 버튼을 클릭해보세요!</div>'
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -806,13 +1051,36 @@ class HighlightManager {
|
||||
|
||||
tooltipHTML += '</div>';
|
||||
|
||||
// 하이라이트 삭제 버튼
|
||||
// 하이라이트 관리 버튼들
|
||||
tooltipHTML += `
|
||||
<div class="mt-4 pt-3 border-t">
|
||||
<button onclick="window.documentViewerInstance.highlightManager.deleteHighlight('${clickedHighlight.id}')"
|
||||
class="text-xs bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600">
|
||||
하이라이트 삭제
|
||||
</button>
|
||||
<div class="mt-4 pt-4 border-t border-gray-200">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-sm font-medium text-gray-700 flex items-center">
|
||||
<svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path fill-rule="evenodd" d="M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z" clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
하이라이트 관리
|
||||
</div>
|
||||
<div class="flex items-center space-x-2">
|
||||
<button onclick="window.documentViewerInstance.highlightManager.duplicateHighlight('${clickedHighlight.id}')"
|
||||
class="text-xs bg-green-500 text-white px-3 py-1 rounded hover:bg-green-600 transition-colors flex items-center"
|
||||
title="하이라이트 복사">
|
||||
<svg class="w-3 h-3 mr-1" fill="currentColor" viewBox="0 0 20 20">
|
||||
<path d="M8 3a1 1 0 011-1h2a1 1 0 110 2H9a1 1 0 01-1-1z"></path>
|
||||
<path d="M6 3a2 2 0 00-2 2v11a2 2 0 002 2h8a2 2 0 002-2V5a2 2 0 00-2-2 3 3 0 01-3 3H9a3 3 0 01-3-3z"></path>
|
||||
</svg>
|
||||
복사
|
||||
</button>
|
||||
<button onclick="window.documentViewerInstance.highlightManager.deleteHighlightWithConfirm('${clickedHighlight.id}')"
|
||||
class="text-xs bg-red-500 text-white px-3 py-1 rounded hover:bg-red-600 transition-colors flex items-center"
|
||||
title="하이라이트 삭제">
|
||||
<svg class="w-3 h-3 mr-1" 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"></path>
|
||||
</svg>
|
||||
삭제
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user