';
tooltip.innerHTML = tooltipHTML;
// 위치 계산 및 표시
const rect = element.getBoundingClientRect();
tooltip.style.top = (rect.bottom + window.scrollY + 10) + 'px';
tooltip.style.left = Math.max(10, rect.left + window.scrollX - 175) + 'px';
document.body.appendChild(tooltip);
// 외부 클릭 시 툴팁 숨기기
setTimeout(() => {
document.addEventListener('click', this.handleTooltipOutsideClick.bind(this));
}, 100);
},
// 백링크 문서로 이동
navigateToBacklinkDocument(sourceDocumentId, backlinkInfo) {
console.log('🔗 백링크로 이동:', sourceDocumentId, backlinkInfo);
// 툴팁 숨기기
this.hideTooltip();
// 백링크 문서로 이동
window.location.href = `/viewer.html?id=${sourceDocumentId}`;
},
// 링크된 문서로 이동 (특정 텍스트 위치 포함)
navigateToLinkedDocument(targetDocumentId, linkInfo) {
let targetUrl = `/viewer.html?id=${targetDocumentId}`;
// 특정 텍스트 위치가 있는 경우 URL에 추가
if (linkInfo && linkInfo.link_type === 'text_fragment' && linkInfo.target_text) {
const params = new URLSearchParams({
highlight_text: linkInfo.target_text,
start_offset: linkInfo.target_start_offset,
end_offset: linkInfo.target_end_offset
});
targetUrl += `&${params.toString()}`;
}
console.log('🔗 링크된 문서로 이동:', targetUrl);
window.location.href = targetUrl;
},
// 기존 navigateToDocument 함수 (백워드 호환성)
navigateToDocument(documentId) {
window.location.href = `/viewer.html?id=${documentId}`;
},
// 특정 텍스트를 하이라이트하고 스크롤
highlightAndScrollToText(targetText, startOffset, endOffset) {
console.log('🎯 highlightAndScrollToText 호출됨:', { targetText, startOffset, endOffset });
const documentContent = document.getElementById('document-content');
if (!documentContent) {
console.error('❌ document-content 요소를 찾을 수 없습니다');
return;
}
// 백링크는 LinkManager가 관리하므로 별도 처리 불필요
console.log('🔗 LinkManager가 백링크를 관리 중');
console.log('📄 문서 내용 길이:', documentContent.textContent.length);
try {
// 임시 하이라이트 적용
console.log('🎨 하이라이트 적용 시작...');
const highlightElement = this.highlightTextRange(
documentContent,
startOffset,
endOffset,
'linked-text-highlight',
{
'style': 'background-color: #FEF3C7 !important; border: 2px solid #F59E0B; border-radius: 4px; padding: 2px;'
}
);
console.log('🔍 하이라이트 요소 결과:', highlightElement);
if (highlightElement) {
console.log('📐 하이라이트 요소 위치:', highlightElement.getBoundingClientRect());
// 해당 요소로 스크롤
highlightElement.scrollIntoView({
behavior: 'smooth',
block: 'center'
});
console.log('✅ 링크된 텍스트로 스크롤 완료');
} else {
console.warn('⚠️ 링크된 텍스트를 찾을 수 없습니다');
console.log('🔍 전체 텍스트 미리보기:', documentContent.textContent.substring(startOffset - 50, endOffset + 50));
}
// 5초 후 하이라이트 제거 및 백링크 복원 (하이라이트 성공 여부와 관계없이)
const self = this;
setTimeout(() => {
const tempHighlight = document.querySelector('.linked-text-highlight');
if (tempHighlight) {
const parent = tempHighlight.parentNode;
parent.replaceChild(document.createTextNode(tempHighlight.textContent), tempHighlight);
parent.normalize();
console.log('🗑️ 임시 하이라이트 제거됨');
}
// 백링크는 LinkManager가 관리하므로 별도 재렌더링 불필요
console.log('✅ 임시 하이라이트 제거 완료 - 백링크는 LinkManager가 유지 관리');
}, 5000);
} catch (error) {
console.error('❌ 텍스트 하이라이트 실패:', error);
}
},
// 텍스트 오프셋 계산 (하이라이트와 동일한 로직)
getTextOffset(container, node, offset) {
let textOffset = 0;
const walker = document.createTreeWalker(
container,
NodeFilter.SHOW_TEXT,
null,
false
);
let currentNode;
while (currentNode = walker.nextNode()) {
if (currentNode === node) {
return textOffset + offset;
}
textOffset += currentNode.textContent.length;
}
return textOffset;
},
// 텍스트 범위 하이라이트 (하이라이트와 동일한 로직, 클래스명만 다름)
highlightTextRange(container, startOffset, endOffset, className, attributes = {}) {
console.log(`🎯 highlightTextRange 호출: ${startOffset}-${endOffset}, 클래스: ${className}`);
const walker = document.createTreeWalker(
container,
NodeFilter.SHOW_TEXT,
null,
false
);
let currentOffset = 0;
let startNode = null;
let startNodeOffset = 0;
let endNode = null;
let endNodeOffset = 0;
// 시작과 끝 노드 찾기
let node;
while (node = walker.nextNode()) {
const nodeLength = node.textContent.length;
if (!startNode && currentOffset + nodeLength > startOffset) {
startNode = node;
startNodeOffset = startOffset - currentOffset;
}
if (currentOffset + nodeLength >= endOffset) {
endNode = node;
endNodeOffset = endOffset - currentOffset;
break;
}
currentOffset += nodeLength;
}
if (!startNode || !endNode) return;
try {
// DOM 변경 전에 Range 유효성 검사
if (!startNode.parentNode || !endNode.parentNode ||
startNodeOffset < 0 || endNodeOffset < 0) {
console.warn(`❌ 유효하지 않은 노드 또는 오프셋 (${className})`);
return null;
}
const range = document.createRange();
// Range 설정 시 예외 처리
try {
range.setStart(startNode, startNodeOffset);
range.setEnd(endNode, endNodeOffset);
} catch (rangeError) {
console.warn(`❌ Range 설정 실패 (${className}):`, rangeError);
return null;
}
// 빈 범위 체크
if (range.collapsed) {
console.warn(`❌ 빈 범위 (${className})`);
range.detach();
return null;
}
const span = document.createElement('span');
span.className = className;
// 속성 추가
Object.entries(attributes).forEach(([key, value]) => {
span.setAttribute(key, value);
});
// 더 안전한 하이라이트 적용 방식
try {
// 범위가 단일 텍스트 노드인지 확인
if (startNode === endNode && startNode.nodeType === Node.TEXT_NODE) {
// 단일 텍스트 노드인 경우 직접 분할
const text = startNode.textContent;
const beforeText = text.substring(0, startNodeOffset);
const highlightText = text.substring(startNodeOffset, endNodeOffset);
const afterText = text.substring(endNodeOffset);
// 새로운 노드들 생성
const parent = startNode.parentNode;
const fragment = document.createDocumentFragment();
if (beforeText) {
fragment.appendChild(document.createTextNode(beforeText));
}
span.textContent = highlightText;
fragment.appendChild(span);
if (afterText) {
fragment.appendChild(document.createTextNode(afterText));
}
// 원본 노드를 새로운 fragment로 교체
parent.replaceChild(fragment, startNode);
console.log(`✅ 안전한 하이라이트 적용: "${highlightText}" (${className})`);
return span;
} else {
// 복잡한 경우 surroundContents 시도
range.surroundContents(span);
console.log(`✅ surroundContents 성공: "${span.textContent}" (${className})`);
return span;
}
} catch (error) {
console.warn(`❌ 하이라이트 적용 실패 (${className}):`, error);
// 실패 시 범위만 표시하고 실제 DOM은 건드리지 않음
return null;
}
} catch (error) {
console.warn(`❌ highlightTextRange 실패 (${className}):`, error);
return null;
}
},
// 백링크 배너 업데이트 (LinkManager 데이터 사용)
updateBacklinkBanner() {
const backlinkCount = this.backlinks ? this.backlinks.length : 0;
const backlinkBanner = document.getElementById('backlink-banner');
if (backlinkBanner) {
const countElement = backlinkBanner.querySelector('.backlink-count');
if (countElement) {
countElement.textContent = backlinkCount;
}
}
},
// 특정 텍스트를 하이라이트하고 스크롤
highlightAndScrollToText(targetText, startOffset, endOffset) {
console.log('🎯 highlightAndScrollToText 호출됨:', { targetText, startOffset, endOffset });
const documentContent = document.getElementById('document-content');
if (!documentContent) {
console.error('❌ document-content 요소를 찾을 수 없습니다');
return;
}
// 백링크는 LinkManager가 관리하므로 별도 처리 불필요
console.log('🔗 LinkManager가 백링크를 관리 중');
console.log('📄 문서 내용 길이:', documentContent.textContent.length);
try {
// 임시 하이라이트 적용
console.log('🎨 하이라이트 적용 시작...');
const highlightElement = this.highlightTextRange(
documentContent,
startOffset,
endOffset,
'linked-text-highlight',
{
'style': 'background-color: #FEF3C7 !important; border: 2px solid #F59E0B; border-radius: 4px; padding: 2px;'
}
);
console.log('🔍 하이라이트 요소 결과:', highlightElement);
if (highlightElement) {
// 요소 위치 가져오기
const rect = highlightElement.getBoundingClientRect();
console.log('📀 하이라이트 요소 위치:', rect);
// 스크롤
const scrollTop = window.pageYOffset + rect.top - window.innerHeight / 2;
window.scrollTo({ top: scrollTop, behavior: 'smooth' });
console.log('✅ 링크된 텍스트로 스크롤 완료');
} else {
console.warn('⚠️ 링크된 텍스트를 찾을 수 없습니다');
console.log('🔍 전체 텍스트 미리보기:', documentContent.textContent.substring(startOffset - 50, endOffset + 50));
}
// 5초 후 하이라이트 제거 및 백링크 복원 (하이라이트 성공 여부와 관계없이)
const self = this;
setTimeout(() => {
const tempHighlight = document.querySelector('.linked-text-highlight');
if (tempHighlight) {
const parent = tempHighlight.parentNode;
parent.replaceChild(document.createTextNode(tempHighlight.textContent), tempHighlight);
parent.normalize();
console.log('🗑️ 임시 하이라이트 제거됨');
}
// 백링크는 LinkManager가 관리하므로 별도 재렌더링 불필요
console.log('✅ 임시 하이라이트 제거 완료 - 백링크는 LinkManager가 유지 관리');
}, 5000);
} catch (error) {
console.error('❌ 텍스트 하이라이트 실패:', error);
}
},
try {
console.log('🔗 백링크 로드 중...');
console.log('📋 현재 문서 ID:', this.documentId);
console.log('📋 현재 문서 제목:', this.documentTitle);
this.backlinks = await window.api.getDocumentBacklinks(this.documentId);
console.log(`✅ 백링크 ${this.backlinks.length}개 로드됨:`, this.backlinks);
// 각 백링크의 상세 정보 출력
this.backlinks.forEach((backlink, index) => {
console.log(`🔗 백링크 ${index + 1}:`);
console.log(` - 소스 문서: ${backlink.source_document_title}`);
console.log(` - 타겟 문서: ${backlink.target_document_title} (현재 문서와 일치해야 함)`);
console.log(` - 선택된 텍스트: "${backlink.selected_text}"`);
console.log(` - 링크 타입: ${backlink.link_type}`);
// 현재 문서와 일치하는지 확인
if (backlink.target_document_id !== this.documentId) {
console.warn(`⚠️ 백링크 타겟 문서 ID 불일치!`);
console.warn(` - 백링크 타겟: ${backlink.target_document_id}`);
console.warn(` - 현재 문서: ${this.documentId}`);
}
});
// Alpine.js 상태 업데이트 강제
if (window.Alpine && window.Alpine.store) {
console.log('🔄 Alpine.js 상태 업데이트 시도...');
}
} catch (error) {
console.error('백링크 로드 실패:', error);
this.backlinks = [];
}
},
navigateToBacklink(backlink) {
console.log('🔗 배너에서 백링크로 이동:', backlink);
// 백링크의 출발 문서로 이동
const url = `/viewer.html?id=${backlink.source_document_id}`;
console.log('🔗 이동할 URL:', url);
window.location.href = url;
},
// 링크 배너에서 링크로 이동
navigateToLink(link) {
console.log('🔗 배너에서 링크로 이동:', link);
// 링크의 대상 문서로 이동
const url = `/viewer.html?id=${link.target_document_id}`;
console.log('🔗 이동할 URL:', url);
// 텍스트 조각 링크인 경우 해당 위치로 스크롤
if (link.link_type === 'text_fragment' && link.target_text) {
const urlWithFragment = `${url}&highlight=${link.target_start_offset}-${link.target_end_offset}&text=${encodeURIComponent(link.target_text)}`;
window.location.href = urlWithFragment;
} else {
window.location.href = url;
}
},
// 고급 링크 기능 메서드들
onTargetDocumentChange() {
// 대상 문서가 변경되면 target_text 초기화
this.linkForm.target_text = '';
this.linkForm.target_start_offset = 0;
this.linkForm.target_end_offset = 0;
},
openTargetDocumentSelector() {
console.log('🎯 openTargetDocumentSelector 함수 호출됨!');
console.log('📋 현재 linkForm.target_document_id:', this.linkForm.target_document_id);
if (!this.linkForm.target_document_id) {
alert('먼저 대상 문서를 선택해주세요.');
return;
}
// 새 창에서 대상 문서 열기 (텍스트 선택 모드 전용 페이지)
const targetUrl = `/text-selector.html?id=${this.linkForm.target_document_id}`;
console.log('🚀 텍스트 선택 창 열기:', targetUrl);
const popup = window.open(targetUrl, 'targetDocumentSelector', 'width=1200,height=800,scrollbars=yes,resizable=yes');
if (!popup) {
console.error('❌ 팝업 창이 차단되었습니다!');
alert('팝업 창이 차단되었습니다. 브라우저 설정에서 팝업을 허용해주세요.');
} else {
console.log('✅ 팝업 창이 성공적으로 열렸습니다');
}
// 팝업에서 텍스트 선택 완료 시 메시지 수신
window.addEventListener('message', (event) => {
if (event.data.type === 'TEXT_SELECTED') {
this.linkForm.target_text = event.data.selectedText;
this.linkForm.target_start_offset = event.data.startOffset;
this.linkForm.target_end_offset = event.data.endOffset;
console.log('🎯 대상 텍스트 선택됨:', event.data);
popup.close();
}
}, { once: true });
},
// 텍스트 선택 모드 초기화
async initTextSelectorMode() {
console.log('🎯 텍스트 선택 모드로 초기화 중...');
// Alpine.js 완전 차단
window.Alpine = {
start: () => console.log('Alpine.js 초기화 차단됨'),
data: () => ({}),
directive: () => {},
magic: () => {},
store: () => ({}),
version: '3.0.0'
};
// 기존 Alpine 인스턴스 제거
if (window.Alpine && window.Alpine.stop) {
window.Alpine.stop();
}
// 인증 확인
if (!api.token) {
window.location.href = '/';
return;
}
try {
// 문서만 로드 (다른 데이터는 불필요)
await this.loadDocument();
// UI 설정
console.log('🔧 텍스트 선택 모드 UI 설정 시작');
this.setupTextSelectorUI();
console.log('✅ 텍스트 선택 모드 UI 설정 완료');
} catch (error) {
console.error('텍스트 선택 모드 초기화 실패:', error);
this.error = '문서를 불러올 수 없습니다: ' + error.message;
} finally {
this.loading = false;
}
},
// 텍스트 선택 모드 UI 설정
setupTextSelectorUI() {
console.log('🔧 setupTextSelectorUI 함수 실행됨');
// 이미 설정되었는지 확인
if (this.textSelectorUISetup) {
console.log('⚠️ 텍스트 선택 모드 UI가 이미 설정됨 - 중복 실행 방지');
return;
}
// 헤더를 텍스트 선택 모드용으로 변경
const header = document.querySelector('header');
console.log('📋 헤더 요소 찾기:', header);
if (header) {
console.log('🎨 헤더 HTML 교체 중...');
// 기존 Alpine 속성 제거
header.removeAttribute('x-data');
header.removeAttribute('x-init');
header.innerHTML = `
텍스트 선택 모드
연결하고 싶은 텍스트를 선택하세요
`;
// 헤더가 다시 변경되지 않도록 보호
header.setAttribute('data-text-selector-mode', 'true');
console.log('🔒 헤더 보호 설정 완료');
// 실제 헤더 내용 확인
console.log('📄 헤더 HTML 확인:', header.innerHTML.substring(0, 200) + '...');
// 언어전환 버튼 확인
const langBtn = header.querySelector('#language-toggle-selector');
console.log('🌐 언어전환 버튼 찾기:', langBtn);
// 취소 버튼 확인
const closeBtn = header.querySelector('button[onclick*="window.close"]');
console.log('❌ 취소 버튼 찾기:', closeBtn);
// 헤더 변경 감지
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList' || mutation.type === 'attributes') {
console.log('⚠️ 헤더가 변경되었습니다!', mutation);
console.log('🔍 변경 후 헤더 내용:', header.innerHTML.substring(0, 100) + '...');
}
});
});
observer.observe(header, {
childList: true,
subtree: true,
attributes: true,
attributeOldValue: true
});
}
// 사이드 패널 숨기기
const aside = document.querySelector('aside');
if (aside) {
aside.style.display = 'none';
}
// 메인 컨텐츠 영역 조정
const main = document.querySelector('main');
if (main) {
main.style.marginRight = '0';
main.classList.add('text-selector-mode');
}
// 문서 콘텐츠에 텍스트 선택 이벤트 추가
const documentContent = document.getElementById('document-content');
if (documentContent) {
documentContent.addEventListener('mouseup', this.handleTextSelectionForLinking.bind(this));
// 선택 가능한 영역임을 시각적으로 표시
documentContent.style.cursor = 'crosshair';
documentContent.style.userSelect = 'text';
// 안내 메시지 추가
const guideDiv = document.createElement('div');
guideDiv.className = 'bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6';
guideDiv.innerHTML = `
텍스트 선택 방법
마우스로 연결하고 싶은 텍스트를 드래그하여 선택하세요.
선택이 완료되면 자동으로 부모 창으로 전달됩니다.
`;
documentContent.parentNode.insertBefore(guideDiv, documentContent);
}
// Alpine.js 컴포넌트 비활성화 (텍스트 선택 모드에서는 불필요)
const alpineElements = document.querySelectorAll('[x-data]');
alpineElements.forEach(el => {
el.removeAttribute('x-data');
});
// 설정 완료 플래그
this.textSelectorUISetup = true;
console.log('✅ 텍스트 선택 모드 UI 설정 완료');
},
// 텍스트 선택 모드에서의 텍스트 선택 처리
handleTextSelectionForLinking() {
const selection = window.getSelection();
if (!selection.rangeCount || selection.isCollapsed) return;
const range = selection.getRangeAt(0);
const selectedText = selection.toString().trim();
if (selectedText.length < 3) {
alert('최소 3글자 이상 선택해주세요.');
return;
}
if (selectedText.length > 500) {
alert('선택된 텍스트가 너무 깁니다. 500자 이하로 선택해주세요.');
return;
}
// 텍스트 오프셋 계산
const documentContent = document.getElementById('document-content');
const { startOffset, endOffset } = this.getTextOffset(documentContent, range);
console.log('🎯 텍스트 선택됨:', {
selectedText,
startOffset,
endOffset
});
// 선택 확인 UI 표시
this.showTextSelectionConfirm(selectedText, startOffset, endOffset);
},
// 텍스트 선택 확인 UI
showTextSelectionConfirm(selectedText, startOffset, endOffset) {
// 기존 확인 UI 제거
const existingConfirm = document.querySelector('.text-selection-confirm');
if (existingConfirm) {
existingConfirm.remove();
}
// 확인 UI 생성
const confirmDiv = document.createElement('div');
confirmDiv.className = 'text-selection-confirm fixed bottom-6 left-1/2 transform -translate-x-1/2 bg-white rounded-lg shadow-2xl border p-6 max-w-md z-50';
// 텍스트 미리보기 (안전하게 처리)
const previewText = selectedText.length > 100 ? selectedText.substring(0, 100) + '...' : selectedText;
confirmDiv.innerHTML = `
텍스트가 선택되었습니다
`;
// 텍스트 안전하게 설정
const previewElement = confirmDiv.querySelector('#selected-text-preview');
previewElement.textContent = `"${previewText}"`;
// 이벤트 리스너 추가
const reselectBtn = confirmDiv.querySelector('#reselect-btn');
const confirmBtn = confirmDiv.querySelector('#confirm-selection-btn');
reselectBtn.addEventListener('click', () => {
confirmDiv.remove();
});
confirmBtn.addEventListener('click', () => {
this.confirmTextSelection(selectedText, startOffset, endOffset);
});
document.body.appendChild(confirmDiv);
// 10초 후 자동 제거 (사용자가 선택하지 않은 경우)
setTimeout(() => {
if (document.contains(confirmDiv)) {
confirmDiv.remove();
}
}, 10000);
},
// 텍스트 선택 확정
confirmTextSelection(selectedText, startOffset, endOffset) {
// 부모 창에 선택된 텍스트 정보 전달
if (window.opener) {
window.opener.postMessage({
type: 'TEXT_SELECTED',
selectedText: selectedText,
startOffset: startOffset,
endOffset: endOffset
}, '*');
console.log('✅ 부모 창에 텍스트 선택 정보 전달됨');
// 성공 메시지 표시 후 창 닫기
const confirmDiv = document.querySelector('.text-selection-confirm');
if (confirmDiv) {
confirmDiv.innerHTML = `
선택 완료!
창이 자동으로 닫힙니다...
`;
setTimeout(() => {
window.close();
}, 1500);
}
} else {
alert('부모 창을 찾을 수 없습니다.');
}
},
// 기능 메뉴 토글
toggleFeatureMenu(feature) {
if (this.activeFeatureMenu === feature) {
this.activeFeatureMenu = null;
} else {
this.activeFeatureMenu = feature;
// 해당 기능의 모달 표시
switch(feature) {
case 'link':
this.showLinksModal = true;
break;
case 'memo':
this.showNotesModal = true;
break;
case 'bookmark':
this.showBookmarksModal = true;
break;
case 'backlink':
this.showBacklinksModal = true;
break;
}
}
},
// 링크 모드 활성화
activateLinkMode() {
if (this.contentType === 'note') {
alert('📝 노트에서는 링크 기능이 향후 지원 예정입니다.');
return;
}
console.log('🔗 링크 모드 활성화 - activateLinkMode 함수 실행됨');
// 이미 선택된 텍스트가 있는지 확인
const selection = window.getSelection();
console.log('📝 현재 선택 상태:', {
rangeCount: selection.rangeCount,
isCollapsed: selection.isCollapsed,
selectedText: selection.toString()
});
if (selection.rangeCount > 0 && !selection.isCollapsed) {
const selectedText = selection.toString().trim();
if (selectedText.length > 0) {
console.log('✅ 선택된 텍스트 발견:', selectedText);
this.selectedText = selectedText;
this.selectedRange = selection.getRangeAt(0);
console.log('🔗 LinkManager로 링크 생성 위임');
this.linkManager.createLinkFromSelection(this.documentId, selectedText, selection.getRangeAt(0));
return;
}
}
// 선택된 텍스트가 없으면 선택 모드 활성화
console.log('📝 텍스트 선택 모드 활성화');
this.activeMode = 'link';
this.showSelectionMessage('텍스트를 선택하세요.');
// 기존 리스너 제거 후 새로 추가
this.removeTextSelectionListener();
this.textSelectionHandler = this.handleTextSelection.bind(this);
document.addEventListener('mouseup', this.textSelectionHandler);
},
// 메모 모드 활성화
activateNoteMode() {
console.log('📝 메모 모드 활성화');
// 이미 선택된 텍스트가 있는지 확인
const selection = window.getSelection();
if (selection.rangeCount > 0 && !selection.isCollapsed) {
const selectedText = selection.toString().trim();
if (selectedText.length > 0) {
console.log('✅ 선택된 텍스트 발견:', selectedText);
this.selectedText = selectedText;
this.selectedRange = selection.getRangeAt(0);
this.createNoteFromSelection();
return;
}
}
// 선택된 텍스트가 없으면 선택 모드 활성화
console.log('📝 텍스트 선택 모드 활성화');
this.activeMode = 'memo';
this.showSelectionMessage('텍스트를 선택하세요.');
// 기존 리스너 제거 후 새로 추가
this.removeTextSelectionListener();
this.textSelectionHandler = this.handleTextSelection.bind(this);
document.addEventListener('mouseup', this.textSelectionHandler);
},
// 책갈피 모드 활성화
activateBookmarkMode() {
console.log('🔖 책갈피 모드 활성화');
// 이미 선택된 텍스트가 있는지 확인
const selection = window.getSelection();
if (selection.rangeCount > 0 && !selection.isCollapsed) {
const selectedText = selection.toString().trim();
if (selectedText.length > 0) {
console.log('✅ 선택된 텍스트 발견:', selectedText);
this.selectedText = selectedText;
this.selectedRange = selection.getRangeAt(0);
this.createBookmarkFromSelection();
return;
}
}
// 선택된 텍스트가 없으면 선택 모드 활성화
console.log('📝 텍스트 선택 모드 활성화');
this.activeMode = 'bookmark';
this.showSelectionMessage('텍스트를 선택하세요.');
// 기존 리스너 제거 후 새로 추가
this.removeTextSelectionListener();
this.textSelectionHandler = this.handleTextSelection.bind(this);
document.addEventListener('mouseup', this.textSelectionHandler);
},
// 텍스트 선택 리스너 제거
removeTextSelectionListener() {
if (this.textSelectionHandler) {
document.removeEventListener('mouseup', this.textSelectionHandler);
this.textSelectionHandler = null;
}
},
// 텍스트 선택 처리
handleTextSelection(event) {
console.log('🎯 텍스트 선택 이벤트 발생');
const selection = window.getSelection();
if (selection.rangeCount > 0 && !selection.isCollapsed) {
const range = selection.getRangeAt(0);
const selectedText = selection.toString().trim();
console.log('📝 선택된 텍스트:', selectedText);
if (selectedText.length > 0) {
this.selectedText = selectedText;
this.selectedRange = range;
// 선택 모드에 따라 다른 동작
console.log('🎯 현재 모드:', this.activeMode);
if (this.activeMode === 'link') {
console.log('🔗 링크 생성 실행');
this.createDocumentLink();
} else if (this.activeMode === 'memo') {
console.log('📝 메모 생성 실행');
this.createNoteFromSelection();
} else if (this.activeMode === 'bookmark') {
console.log('🔖 책갈피 생성 실행');
this.createBookmarkFromSelection();
}
// 모드 해제
this.activeMode = null;
this.hideSelectionMessage();
this.removeTextSelectionListener();
}
}
},
// 선택 메시지 표시
showSelectionMessage(message) {
// 기존 메시지 제거
const existingMessage = document.querySelector('.selection-message');
if (existingMessage) {
existingMessage.remove();
}
// 새 메시지 생성
const messageDiv = document.createElement('div');
messageDiv.className = 'selection-message fixed top-20 left-1/2 transform -translate-x-1/2 bg-blue-500 text-white px-4 py-2 rounded-lg shadow-lg z-50';
messageDiv.textContent = message;
document.body.appendChild(messageDiv);
},
// 선택 메시지 숨기기
hideSelectionMessage() {
const existingMessage = document.querySelector('.selection-message');
if (existingMessage) {
existingMessage.remove();
}
},
// 링크 생성 UI 표시
showLinkCreationUI() {
this.createDocumentLink();
},
// 선택된 텍스트로 메모 생성 (HighlightManager로 위임)
async createNoteFromSelection() {
if (!this.selectedText || !this.selectedRange) return;
try {
// HighlightManager의 상태 설정
this.highlightManager.selectedText = this.selectedText;
this.highlightManager.selectedRange = this.selectedRange;
this.highlightManager.selectedHighlightColor = '#FFFF00';
// HighlightManager의 createNoteFromSelection 호출
await this.highlightManager.createNoteFromSelection(this.documentId, this.contentType);
// 상태 동기화
this.highlights = this.highlightManager.highlights;
this.notes = this.highlightManager.notes;
return;
} catch (error) {
console.error('메모 생성 실패:', error);
alert('메모 생성에 실패했습니다.');
}
},
// 선택된 텍스트로 책갈피 생성
async createBookmarkFromSelection() {
if (!this.selectedText || !this.selectedRange) return;
try {
// 하이라이트 생성 (책갈피는 주황색)
const highlightData = await this.createHighlight(this.selectedText, this.selectedRange, '#FFA500');
// 책갈피 생성
const bookmarkData = {
highlight_id: highlightData.id,
title: this.selectedText.substring(0, 50) + (this.selectedText.length > 50 ? '...' : '')
};
const bookmark = await api.createBookmark(this.documentId, bookmarkData);
// 데이터 새로고침
await this.loadBookmarks();
alert('책갈피가 생성되었습니다.');
} catch (error) {
console.error('책갈피 생성 실패:', error);
alert('책갈피 생성에 실패했습니다.');
}
},
// 대상 선택 초기화
resetTargetSelection() {
this.linkForm.target_book_id = '';
this.linkForm.target_document_id = '';
this.linkForm.target_text = '';
this.linkForm.target_start_offset = null;
this.linkForm.target_end_offset = null;
this.filteredDocuments = [];
// 같은 서적인 경우 현재 서적의 문서들 로드
if (this.linkForm.book_scope === 'same') {
this.loadSameBookDocuments();
}
},
// 같은 서적의 문서들 로드
async loadSameBookDocuments() {
try {
if (this.navigation?.book_info?.id) {
// 현재 서적의 문서들만 가져오기
const allDocuments = await api.getLinkableDocuments(this.documentId);
this.filteredDocuments = allDocuments.filter(doc =>
doc.book_id === this.navigation.book_info.id && doc.id !== this.documentId
);
console.log('📚 같은 서적 문서들:', this.filteredDocuments);
} else {
// 서적 정보가 없으면 모든 문서
this.filteredDocuments = await api.getLinkableDocuments(this.documentId);
}
} catch (error) {
console.error('같은 서적 문서 로드 실패:', error);
this.filteredDocuments = [];
}
},
// 서적별 문서 로드
async loadDocumentsFromBook() {
try {
if (this.linkForm.target_book_id) {
// 선택된 서적의 문서들만 가져오기
const allDocuments = await api.getLinkableDocuments(this.documentId);
this.filteredDocuments = allDocuments.filter(doc =>
doc.book_id === this.linkForm.target_book_id
);
console.log('📚 선택된 서적 문서들:', this.filteredDocuments);
} else {
this.filteredDocuments = [];
}
// 문서 선택 초기화
this.linkForm.target_document_id = '';
} catch (error) {
console.error('서적별 문서 로드 실패:', error);
this.filteredDocuments = [];
}
},
// 사용 가능한 서적 목록 로드
async loadAvailableBooks() {
try {
console.log('📚 서적 목록 로딩 시작...');
// 문서 목록에서 서적 정보 추출
const allDocuments = await api.getLinkableDocuments(this.documentId);
console.log('📄 모든 문서들:', allDocuments);
// 서적별로 그룹화
const bookMap = new Map();
allDocuments.forEach(doc => {
if (doc.book_id && doc.book_title) {
bookMap.set(doc.book_id, {
id: doc.book_id,
title: doc.book_title
});
}
});
// 현재 서적 제외
const currentBookId = this.navigation?.book_info?.id;
if (currentBookId) {
bookMap.delete(currentBookId);
}
this.availableBooks = Array.from(bookMap.values());
console.log('📚 사용 가능한 서적들:', this.availableBooks);
console.log('🔍 현재 서적 ID:', currentBookId);
} catch (error) {
console.error('서적 목록 로드 실패:', error);
this.availableBooks = [];
}
},
// 선택된 서적 제목 가져오기
getSelectedBookTitle() {
const selectedBook = this.availableBooks.find(book => book.id === this.linkForm.target_book_id);
return selectedBook ? selectedBook.title : '';
}
});