🎨 데본씽크 스타일 통합 미리보기 완성
📱 PDF 미리보기 (PDF.js): - 실제 PDF 렌더링 (Canvas 기반) - 줌 인/아웃 (50% ~ 300%) - 페이지 네비게이션 (이전/다음/직접입력) - 고해상도 디스플레이 지원 - 검색어 위치로 뷰어 연동 📄 HTML 문서 미리보기: - iframe 렌더링 뷰 / 소스 코드 뷰 토글 - 검색어 자동 하이라이트 (iframe 내부) - 문법 하이라이트된 소스 코드 표시 - 안전한 sandbox 모드 📝 노트 문서 미리보기: - 제목, 생성일시 표시 - 검색어 하이라이트 - 편집기에서 열기 버튼 - 깔끔한 카드 스타일 UI 🌳 메모 트리 노드 미리보기: - 메모 제목과 내용 표시 - 트리 정보 (소속 트리명) - 검색어 하이라이트 - 구조화된 레이아웃 🎯 UX 개선: - 타입별 아이콘과 색상 구분 - ESC 키 / 배경 클릭으로 닫기 - 로딩 상태 표시 - 에러 처리 및 fallback - 반응형 디자인
This commit is contained in:
@@ -8,6 +8,9 @@
|
|||||||
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js" defer></script>
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||||
|
|
||||||
|
<!-- PDF.js 라이브러리 -->
|
||||||
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js"></script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
[x-cloak] { display: none !important; }
|
[x-cloak] { display: none !important; }
|
||||||
|
|
||||||
@@ -456,7 +459,7 @@
|
|||||||
|
|
||||||
<!-- 모달 내용 -->
|
<!-- 모달 내용 -->
|
||||||
<div class="p-6 overflow-y-auto max-h-[60vh]">
|
<div class="p-6 overflow-y-auto max-h-[60vh]">
|
||||||
<!-- PDF 미리보기 -->
|
<!-- PDF 미리보기 (데본씽크 스타일) -->
|
||||||
<div x-show="previewResult?.type === 'document_content' && previewResult?.highlight_info?.has_pdf"
|
<div x-show="previewResult?.type === 'document_content' && previewResult?.highlight_info?.has_pdf"
|
||||||
class="mb-4">
|
class="mb-4">
|
||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
@@ -464,26 +467,172 @@
|
|||||||
<i class="fas fa-file-pdf mr-2 text-red-600"></i>PDF 미리보기
|
<i class="fas fa-file-pdf mr-2 text-red-600"></i>PDF 미리보기
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center space-x-2">
|
<div class="flex items-center space-x-2">
|
||||||
|
<button @click="zoomOut()"
|
||||||
|
class="px-2 py-1 bg-gray-600 text-white rounded text-xs hover:bg-gray-700">
|
||||||
|
<i class="fas fa-search-minus"></i>
|
||||||
|
</button>
|
||||||
|
<span class="text-xs text-gray-600" x-text="`${Math.round(pdfZoom * 100)}%`"></span>
|
||||||
|
<button @click="zoomIn()"
|
||||||
|
class="px-2 py-1 bg-gray-600 text-white rounded text-xs hover:bg-gray-700">
|
||||||
|
<i class="fas fa-search-plus"></i>
|
||||||
|
</button>
|
||||||
<button @click="searchInPdf()"
|
<button @click="searchInPdf()"
|
||||||
class="px-3 py-1 bg-blue-600 text-white rounded text-xs hover:bg-blue-700">
|
class="px-3 py-1 bg-blue-600 text-white rounded text-xs hover:bg-blue-700">
|
||||||
<i class="fas fa-search mr-1"></i>PDF에서 검색
|
<i class="fas fa-search mr-1"></i>PDF에서 검색
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="border rounded-lg overflow-hidden bg-gray-100" style="height: 400px;">
|
|
||||||
<iframe :src="`/api/documents/${previewResult?.document_id}/pdf`"
|
<!-- PDF 뷰어 컨테이너 -->
|
||||||
class="w-full h-full"
|
<div class="border rounded-lg overflow-hidden bg-gray-100 relative" style="height: 500px;">
|
||||||
x-show="!pdfError"
|
<!-- PDF.js 뷰어 -->
|
||||||
@error="pdfError = true">
|
<div id="pdfViewerContainer" class="w-full h-full overflow-auto bg-gray-200">
|
||||||
|
<div x-show="pdfLoading" class="flex items-center justify-center h-full">
|
||||||
|
<div class="text-center">
|
||||||
|
<i class="fas fa-spinner fa-spin text-2xl text-gray-500 mb-2"></i>
|
||||||
|
<p class="text-gray-600">PDF를 로드하는 중...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<canvas id="pdfCanvas"
|
||||||
|
x-show="!pdfLoading && !pdfError"
|
||||||
|
class="mx-auto shadow-lg"
|
||||||
|
style="display: block; margin: 20px auto;"></canvas>
|
||||||
|
|
||||||
|
<div x-show="pdfError" class="flex items-center justify-center h-full text-gray-500">
|
||||||
|
<div class="text-center">
|
||||||
|
<i class="fas fa-exclamation-triangle text-2xl mb-2"></i>
|
||||||
|
<p>PDF를 로드할 수 없습니다</p>
|
||||||
|
<button @click="openResult(previewResult)"
|
||||||
|
class="mt-2 px-3 py-1 bg-blue-600 text-white rounded text-sm">
|
||||||
|
뷰어에서 열기
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 페이지 네비게이션 -->
|
||||||
|
<div x-show="!pdfLoading && !pdfError && pdfTotalPages > 1"
|
||||||
|
class="absolute bottom-4 left-1/2 transform -translate-x-1/2 bg-black bg-opacity-75 text-white px-4 py-2 rounded-lg flex items-center space-x-3">
|
||||||
|
<button @click="prevPage()"
|
||||||
|
:disabled="pdfCurrentPage <= 1"
|
||||||
|
class="px-2 py-1 bg-gray-600 rounded text-xs hover:bg-gray-500 disabled:opacity-50 disabled:cursor-not-allowed">
|
||||||
|
<i class="fas fa-chevron-left"></i>
|
||||||
|
</button>
|
||||||
|
<span class="text-sm">
|
||||||
|
<input type="number"
|
||||||
|
x-model="pdfCurrentPage"
|
||||||
|
@change="goToPage(pdfCurrentPage)"
|
||||||
|
:min="1"
|
||||||
|
:max="pdfTotalPages"
|
||||||
|
class="w-12 text-center bg-transparent border-b border-gray-400 text-white text-xs">
|
||||||
|
/ <span x-text="pdfTotalPages"></span>
|
||||||
|
</span>
|
||||||
|
<button @click="nextPage()"
|
||||||
|
:disabled="pdfCurrentPage >= pdfTotalPages"
|
||||||
|
class="px-2 py-1 bg-gray-600 rounded text-xs hover:bg-gray-500 disabled:opacity-50 disabled:cursor-not-allowed">
|
||||||
|
<i class="fas fa-chevron-right"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- HTML 문서 미리보기 -->
|
||||||
|
<div x-show="(previewResult?.type === 'document' || previewResult?.type === 'document_content') && !previewResult?.highlight_info?.has_pdf"
|
||||||
|
class="mb-4">
|
||||||
|
<div class="flex items-center justify-between mb-3">
|
||||||
|
<div class="text-sm font-medium text-gray-800">
|
||||||
|
<i class="fas fa-code mr-2 text-green-600"></i>HTML 문서 미리보기
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<button @click="toggleHtmlRaw()"
|
||||||
|
class="px-3 py-1 bg-gray-600 text-white rounded text-xs hover:bg-gray-700">
|
||||||
|
<i class="fas fa-code mr-1"></i><span x-text="htmlRawMode ? '렌더링' : '소스'"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border rounded-lg overflow-hidden bg-white relative" style="height: 500px;">
|
||||||
|
<!-- HTML 렌더링 뷰 -->
|
||||||
|
<iframe id="htmlPreviewFrame"
|
||||||
|
x-show="!htmlRawMode && !htmlLoading"
|
||||||
|
class="w-full h-full border-0"
|
||||||
|
sandbox="allow-same-origin">
|
||||||
</iframe>
|
</iframe>
|
||||||
<div x-show="pdfError" class="flex items-center justify-center h-full text-gray-500">
|
|
||||||
|
<!-- HTML 소스 뷰 -->
|
||||||
|
<div x-show="htmlRawMode && !htmlLoading"
|
||||||
|
class="w-full h-full overflow-auto p-4 bg-gray-900 text-green-400 font-mono text-sm">
|
||||||
|
<pre x-html="htmlSourceCode"></pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 로딩 상태 -->
|
||||||
|
<div x-show="htmlLoading" class="flex items-center justify-center h-full">
|
||||||
<div class="text-center">
|
<div class="text-center">
|
||||||
<i class="fas fa-exclamation-triangle text-2xl mb-2"></i>
|
<i class="fas fa-spinner fa-spin text-2xl text-gray-500 mb-2"></i>
|
||||||
<p>PDF를 로드할 수 없습니다</p>
|
<p class="text-gray-600">HTML을 로드하는 중...</p>
|
||||||
<button @click="openResult(previewResult)"
|
</div>
|
||||||
class="mt-2 px-3 py-1 bg-blue-600 text-white rounded text-sm">
|
</div>
|
||||||
뷰어에서 열기
|
</div>
|
||||||
</button>
|
</div>
|
||||||
|
|
||||||
|
<!-- 메모 트리 노드 미리보기 -->
|
||||||
|
<div x-show="previewResult?.type === 'memo'"
|
||||||
|
class="mb-4">
|
||||||
|
<div class="flex items-center justify-between mb-3">
|
||||||
|
<div class="text-sm font-medium text-gray-800">
|
||||||
|
<i class="fas fa-tree mr-2 text-purple-600"></i>메모 노드 미리보기
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<span class="text-xs text-gray-600" x-text="previewResult?.document_title"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border rounded-lg overflow-hidden bg-white" style="height: 400px;">
|
||||||
|
<div class="h-full overflow-auto p-6">
|
||||||
|
<!-- 메모 제목 -->
|
||||||
|
<h3 class="text-xl font-bold text-gray-900 mb-4" x-text="previewResult?.title"></h3>
|
||||||
|
|
||||||
|
<!-- 메모 내용 -->
|
||||||
|
<div class="prose max-w-none">
|
||||||
|
<div class="text-gray-700 leading-relaxed whitespace-pre-wrap"
|
||||||
|
x-html="highlightText(previewResult?.content || '', searchQuery)"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 노트 문서 미리보기 -->
|
||||||
|
<div x-show="previewResult?.type === 'note'"
|
||||||
|
class="mb-4">
|
||||||
|
<div class="flex items-center justify-between mb-3">
|
||||||
|
<div class="text-sm font-medium text-gray-800">
|
||||||
|
<i class="fas fa-sticky-note mr-2 text-blue-600"></i>노트 미리보기
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<button @click="toggleNoteEdit()"
|
||||||
|
class="px-3 py-1 bg-blue-600 text-white rounded text-xs hover:bg-blue-700">
|
||||||
|
<i class="fas fa-edit mr-1"></i>편집기에서 열기
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="border rounded-lg overflow-hidden bg-white" style="height: 450px;">
|
||||||
|
<div class="h-full overflow-auto">
|
||||||
|
<!-- 노트 헤더 -->
|
||||||
|
<div class="p-4 border-b bg-gray-50">
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900" x-text="previewResult?.title"></h3>
|
||||||
|
<div class="text-sm text-gray-600 mt-1">
|
||||||
|
<span x-text="formatDate(previewResult?.created_at)"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 노트 내용 -->
|
||||||
|
<div class="p-6">
|
||||||
|
<div class="prose max-w-none">
|
||||||
|
<div class="text-gray-700 leading-relaxed"
|
||||||
|
x-html="highlightText(previewResult?.content || '', searchQuery)"></div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -26,6 +26,18 @@ window.searchApp = function() {
|
|||||||
previewLoading: false,
|
previewLoading: false,
|
||||||
pdfError: false,
|
pdfError: false,
|
||||||
|
|
||||||
|
// PDF 뷰어 상태
|
||||||
|
pdfLoading: false,
|
||||||
|
pdfDoc: null,
|
||||||
|
pdfCurrentPage: 1,
|
||||||
|
pdfTotalPages: 0,
|
||||||
|
pdfZoom: 1.0,
|
||||||
|
|
||||||
|
// HTML 뷰어 상태
|
||||||
|
htmlLoading: false,
|
||||||
|
htmlRawMode: false,
|
||||||
|
htmlSourceCode: '',
|
||||||
|
|
||||||
// 인증 상태
|
// 인증 상태
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
currentUser: null,
|
currentUser: null,
|
||||||
@@ -194,9 +206,27 @@ window.searchApp = function() {
|
|||||||
this.previewLoading = true;
|
this.previewLoading = true;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 추가 내용 로드 (필요한 경우)
|
// 타입별 미리보기 로드
|
||||||
if (result.type === 'document' || result.type === 'note') {
|
if (result.type === 'document_content' && result.highlight_info?.has_pdf) {
|
||||||
// 전체 내용 로드
|
// PDF 미리보기
|
||||||
|
await this.loadPdfPreview(result.document_id);
|
||||||
|
} else if ((result.type === 'document' || result.type === 'document_content') && !result.highlight_info?.has_pdf) {
|
||||||
|
// HTML 문서 미리보기
|
||||||
|
await this.loadHtmlPreview(result.document_id);
|
||||||
|
} else if (result.type === 'note') {
|
||||||
|
// 노트 미리보기 - 전체 내용 로드
|
||||||
|
const fullContent = await this.loadFullContent(result);
|
||||||
|
if (fullContent) {
|
||||||
|
this.previewResult = { ...result, content: fullContent };
|
||||||
|
}
|
||||||
|
} else if (result.type === 'memo') {
|
||||||
|
// 메모 미리보기 - 전체 내용 로드
|
||||||
|
const fullContent = await this.loadFullContent(result);
|
||||||
|
if (fullContent) {
|
||||||
|
this.previewResult = { ...result, content: fullContent };
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 기타 타입 - 기본 내용 로드
|
||||||
const fullContent = await this.loadFullContent(result);
|
const fullContent = await this.loadFullContent(result);
|
||||||
if (fullContent) {
|
if (fullContent) {
|
||||||
this.previewResult = { ...result, content: fullContent };
|
this.previewResult = { ...result, content: fullContent };
|
||||||
@@ -283,6 +313,229 @@ window.searchApp = function() {
|
|||||||
this.previewResult = null;
|
this.previewResult = null;
|
||||||
this.previewLoading = false;
|
this.previewLoading = false;
|
||||||
this.pdfError = false;
|
this.pdfError = false;
|
||||||
|
|
||||||
|
// PDF 리소스 정리
|
||||||
|
this.pdfDoc = null;
|
||||||
|
this.pdfCurrentPage = 1;
|
||||||
|
this.pdfTotalPages = 0;
|
||||||
|
this.pdfZoom = 1.0;
|
||||||
|
this.pdfLoading = false;
|
||||||
|
|
||||||
|
// HTML 리소스 정리
|
||||||
|
this.htmlLoading = false;
|
||||||
|
this.htmlRawMode = false;
|
||||||
|
this.htmlSourceCode = '';
|
||||||
|
},
|
||||||
|
|
||||||
|
// PDF 미리보기 로드
|
||||||
|
async loadPdfPreview(documentId) {
|
||||||
|
this.pdfLoading = true;
|
||||||
|
this.pdfError = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// PDF.js 워커 설정
|
||||||
|
if (typeof pdfjsLib !== 'undefined') {
|
||||||
|
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js';
|
||||||
|
}
|
||||||
|
|
||||||
|
const pdfUrl = `/api/documents/${documentId}/pdf`;
|
||||||
|
|
||||||
|
// PDF 문서 로드
|
||||||
|
const loadingTask = pdfjsLib.getDocument({
|
||||||
|
url: pdfUrl,
|
||||||
|
httpHeaders: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.pdfDoc = await loadingTask.promise;
|
||||||
|
this.pdfTotalPages = this.pdfDoc.numPages;
|
||||||
|
this.pdfCurrentPage = 1;
|
||||||
|
|
||||||
|
console.log('PDF 로드 완료:', this.pdfTotalPages, '페이지');
|
||||||
|
|
||||||
|
// 첫 페이지 렌더링
|
||||||
|
await this.renderPdfPage(1);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('PDF 로드 실패:', error);
|
||||||
|
this.pdfError = true;
|
||||||
|
} finally {
|
||||||
|
this.pdfLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// PDF 페이지 렌더링
|
||||||
|
async renderPdfPage(pageNum) {
|
||||||
|
if (!this.pdfDoc) return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const page = await this.pdfDoc.getPage(pageNum);
|
||||||
|
const canvas = document.getElementById('pdfCanvas');
|
||||||
|
const context = canvas.getContext('2d');
|
||||||
|
|
||||||
|
// 뷰포트 설정 (줌 적용)
|
||||||
|
const viewport = page.getViewport({ scale: this.pdfZoom });
|
||||||
|
|
||||||
|
// 캔버스 크기 설정
|
||||||
|
canvas.height = viewport.height;
|
||||||
|
canvas.width = viewport.width;
|
||||||
|
|
||||||
|
// 고해상도 디스플레이 지원
|
||||||
|
const devicePixelRatio = window.devicePixelRatio || 1;
|
||||||
|
canvas.style.width = viewport.width + 'px';
|
||||||
|
canvas.style.height = viewport.height + 'px';
|
||||||
|
canvas.width = viewport.width * devicePixelRatio;
|
||||||
|
canvas.height = viewport.height * devicePixelRatio;
|
||||||
|
context.scale(devicePixelRatio, devicePixelRatio);
|
||||||
|
|
||||||
|
// 페이지 렌더링
|
||||||
|
const renderContext = {
|
||||||
|
canvasContext: context,
|
||||||
|
viewport: viewport
|
||||||
|
};
|
||||||
|
|
||||||
|
await page.render(renderContext).promise;
|
||||||
|
console.log('페이지 렌더링 완료:', pageNum);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('페이지 렌더링 실패:', error);
|
||||||
|
this.pdfError = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 줌 인
|
||||||
|
async zoomIn() {
|
||||||
|
if (this.pdfZoom < 3.0) {
|
||||||
|
this.pdfZoom += 0.25;
|
||||||
|
await this.renderPdfPage(this.pdfCurrentPage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 줌 아웃
|
||||||
|
async zoomOut() {
|
||||||
|
if (this.pdfZoom > 0.5) {
|
||||||
|
this.pdfZoom -= 0.25;
|
||||||
|
await this.renderPdfPage(this.pdfCurrentPage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 이전 페이지
|
||||||
|
async prevPage() {
|
||||||
|
if (this.pdfCurrentPage > 1) {
|
||||||
|
this.pdfCurrentPage--;
|
||||||
|
await this.renderPdfPage(this.pdfCurrentPage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 다음 페이지
|
||||||
|
async nextPage() {
|
||||||
|
if (this.pdfCurrentPage < this.pdfTotalPages) {
|
||||||
|
this.pdfCurrentPage++;
|
||||||
|
await this.renderPdfPage(this.pdfCurrentPage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 특정 페이지로 이동
|
||||||
|
async goToPage(pageNum) {
|
||||||
|
const page = parseInt(pageNum);
|
||||||
|
if (page >= 1 && page <= this.pdfTotalPages) {
|
||||||
|
this.pdfCurrentPage = page;
|
||||||
|
await this.renderPdfPage(this.pdfCurrentPage);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// HTML 미리보기 로드
|
||||||
|
async loadHtmlPreview(documentId) {
|
||||||
|
this.htmlLoading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// HTML 내용 가져오기
|
||||||
|
const response = await fetch(`/api/documents/${documentId}/content`, {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const htmlContent = await response.text();
|
||||||
|
this.htmlSourceCode = this.escapeHtml(htmlContent);
|
||||||
|
|
||||||
|
// iframe에 HTML 로드
|
||||||
|
const iframe = document.getElementById('htmlPreviewFrame');
|
||||||
|
if (iframe) {
|
||||||
|
const doc = iframe.contentDocument || iframe.contentWindow.document;
|
||||||
|
doc.open();
|
||||||
|
doc.write(htmlContent);
|
||||||
|
doc.close();
|
||||||
|
|
||||||
|
// 검색어 하이라이트 (iframe 내부)
|
||||||
|
if (this.searchQuery) {
|
||||||
|
this.highlightInIframe(iframe, this.searchQuery);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('HTML 로드 실패');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('HTML 미리보기 로드 실패:', error);
|
||||||
|
} finally {
|
||||||
|
this.htmlLoading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// HTML 소스/렌더링 모드 토글
|
||||||
|
toggleHtmlRaw() {
|
||||||
|
this.htmlRawMode = !this.htmlRawMode;
|
||||||
|
},
|
||||||
|
|
||||||
|
// iframe 내부 검색어 하이라이트
|
||||||
|
highlightInIframe(iframe, query) {
|
||||||
|
try {
|
||||||
|
const doc = iframe.contentDocument || iframe.contentWindow.document;
|
||||||
|
const walker = doc.createTreeWalker(
|
||||||
|
doc.body,
|
||||||
|
NodeFilter.SHOW_TEXT,
|
||||||
|
null,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
|
||||||
|
const textNodes = [];
|
||||||
|
let node;
|
||||||
|
while (node = walker.nextNode()) {
|
||||||
|
if (node.textContent.toLowerCase().includes(query.toLowerCase())) {
|
||||||
|
textNodes.push(node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
textNodes.forEach(textNode => {
|
||||||
|
const parent = textNode.parentNode;
|
||||||
|
const text = textNode.textContent;
|
||||||
|
const regex = new RegExp(`(${this.escapeRegExp(query)})`, 'gi');
|
||||||
|
const highlightedHTML = text.replace(regex, '<mark style="background: yellow; color: black;">$1</mark>');
|
||||||
|
|
||||||
|
const wrapper = doc.createElement('span');
|
||||||
|
wrapper.innerHTML = highlightedHTML;
|
||||||
|
parent.replaceChild(wrapper, textNode);
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('iframe 하이라이트 실패:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// HTML 이스케이프
|
||||||
|
escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 노트 편집기에서 열기
|
||||||
|
toggleNoteEdit() {
|
||||||
|
if (this.previewResult && this.previewResult.type === 'note') {
|
||||||
|
const url = `/note-editor.html?id=${this.previewResult.id}`;
|
||||||
|
window.open(url, '_blank');
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// PDF에서 검색
|
// PDF에서 검색
|
||||||
|
|||||||
Reference in New Issue
Block a user