Add comprehensive PDF reader enhancements
- pdf-page-sizes.ts: Dynamic page sizing and layout detection - pdf-rotation.ts: Page rotation with orientation detection - pdf-minimap.ts: Thumbnail minimap for navigation - pdf-dual-page.ts: Dual page spread support - pdf-links.ts: Clickable link handling - pdf-clipbooard.ts: Copy to clipboard functionality - pdf-bookmarks.ts: Bookmark management - pdf-outline.ts: Document outline/TOC integration - pdf-text-selection.ts: Text selection and highlighting - page-cache.ts: PDF page caching system - pdf-search.ts: Full-text search within PDF - pdf-navigation.ts: Navigation controls and history - annotation-layer.ts: PDF annotation rendering - text-layer-renderer.ts: Text layer overlay rendering - pdfjs-wrapper.ts: PDF.js wrapper with utilities
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
// Mini-map navigation for PDF pages
|
||||
// Procedural implementation (no OOP)
|
||||
|
||||
interface PDFMiniMapState {
|
||||
miniMap: HTMLElement;
|
||||
currentPage: number;
|
||||
totalPages: number;
|
||||
thumbnails: Map<number, HTMLCanvasElement>;
|
||||
onPageNavigate: (pageNumber: number) => void;
|
||||
}
|
||||
|
||||
function createPDFMiniMap(
|
||||
container: HTMLElement,
|
||||
onPageNavigate: (pageNumber: number) => void,
|
||||
): PDFMiniMapState {
|
||||
const miniMap = createMiniMapElement(container);
|
||||
container.appendChild(miniMap);
|
||||
|
||||
return {
|
||||
miniMap,
|
||||
currentPage: 1,
|
||||
totalPages: 0,
|
||||
thumbnails: new Map(),
|
||||
onPageNavigate,
|
||||
};
|
||||
}
|
||||
|
||||
function createMiniMapElement(container: HTMLElement): HTMLElement {
|
||||
const miniMap = document.createElement("div");
|
||||
miniMap.className = "pdf-minimap";
|
||||
miniMap.innerHTML = `
|
||||
<div class="pdf-minimap-header">Pages</div>
|
||||
<div class="pdf-minimap-thumbnails"></div>
|
||||
<div class="pdf-minimap-indicator"></div>
|
||||
`;
|
||||
|
||||
const style = document.createElement("style");
|
||||
style.textContent = getMiniMapStyles();
|
||||
miniMap.appendChild(style);
|
||||
|
||||
return miniMap;
|
||||
}
|
||||
|
||||
async function initializePDFMiniMap(
|
||||
state: PDFMiniMapState,
|
||||
totalPages: number,
|
||||
renderThumbnail: (page: number) => Promise<HTMLCanvasElement>,
|
||||
): Promise<PDFMiniMapState> {
|
||||
const newState = { ...state, totalPages };
|
||||
|
||||
await generateMiniMapThumbnails(newState, renderThumbnail);
|
||||
setupMiniMapEventListeners(newState);
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
async function generateMiniMapThumbnails(
|
||||
state: PDFMiniMapState,
|
||||
renderThumbnail: (page: number) => Promise<HTMLCanvasElement>,
|
||||
): Promise<void> {
|
||||
const container = state.miniMap.querySelector(
|
||||
".pdf-minimap-thumbnails",
|
||||
) as HTMLElement;
|
||||
container.innerHTML = "";
|
||||
|
||||
for (let page = 1; page <= state.totalPages; page++) {
|
||||
try {
|
||||
const thumbnail = await renderThumbnail(page);
|
||||
thumbnail.className = "pdf-minimap-thumbnail";
|
||||
thumbnail.dataset.pageNumber = page.toString();
|
||||
thumbnail.style.width = "80px";
|
||||
thumbnail.style.height = "auto";
|
||||
thumbnail.style.cursor = "pointer";
|
||||
thumbnail.style.marginBottom = "4px";
|
||||
|
||||
container.appendChild(thumbnail);
|
||||
state.thumbnails.set(page, thumbnail);
|
||||
} catch (error) {
|
||||
console.error(`Failed to generate thumbnail for page ${page}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function setupMiniMapEventListeners(state: PDFMiniMapState): void {
|
||||
const container = state.miniMap.querySelector(".pdf-minimap-thumbnails");
|
||||
|
||||
container?.addEventListener("click", (e) => {
|
||||
const thumbnail = (e.target as HTMLElement).closest(
|
||||
".pdf-minimap-thumbnail",
|
||||
) as HTMLElement;
|
||||
if (thumbnail) {
|
||||
const pageNumber = parseInt(thumbnail.dataset.pageNumber || "1");
|
||||
state.onPageNavigate(pageNumber);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function updateMiniMapCurrentPage(
|
||||
state: PDFMiniMapState,
|
||||
pageNumber: number,
|
||||
): PDFMiniMapState {
|
||||
const indicator = state.miniMap.querySelector(
|
||||
".pdf-minimap-indicator",
|
||||
) as HTMLElement;
|
||||
const thumbnail = state.thumbnails.get(pageNumber);
|
||||
|
||||
if (thumbnail && indicator) {
|
||||
const rect = thumbnail.getBoundingClientRect();
|
||||
indicator.style.top = `${thumbnail.offsetTop}px`;
|
||||
indicator.style.height = `${rect.height}px`;
|
||||
}
|
||||
|
||||
state.thumbnails.forEach((thumb, page) => {
|
||||
if (page === pageNumber) {
|
||||
thumb.style.outline = "2px solid var(--accent)";
|
||||
thumb.style.opacity = "1";
|
||||
} else {
|
||||
thumb.style.outline = "none";
|
||||
thumb.style.opacity = "0.7";
|
||||
}
|
||||
});
|
||||
|
||||
return { ...state, currentPage: pageNumber };
|
||||
}
|
||||
|
||||
function showMiniMap(state: PDFMiniMapState): void {
|
||||
state.miniMap.style.display = "block";
|
||||
}
|
||||
|
||||
function hideMiniMap(state: PDFMiniMapState): void {
|
||||
state.miniMap.style.display = "none";
|
||||
}
|
||||
|
||||
function toggleMiniMap(state: PDFMiniMapState): void {
|
||||
const isVisible = state.miniMap.style.display !== "none";
|
||||
state.miniMap.style.display = isVisible ? "none" : "block";
|
||||
}
|
||||
|
||||
function getMiniMapStyles(): string {
|
||||
return `
|
||||
.pdf-minimap {
|
||||
position: fixed;
|
||||
right: 20px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 100px;
|
||||
max-height: 80vh;
|
||||
background: var(--bg-primary);
|
||||
border: 1px solid var(--text-secondary);
|
||||
border-radius: 8px;
|
||||
padding: 8px;
|
||||
overflow-y: auto;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.pdf-minimap-header {
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
margin-bottom: 8px;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.pdf-minimap-thumbnails {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.pdf-minimap-thumbnail {
|
||||
transition: outline 0.2s, opacity 0.2s;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.pdf-minimap-thumbnail:hover {
|
||||
opacity: 1 !important;
|
||||
outline: 1px solid var(--text-secondary) !important;
|
||||
}
|
||||
|
||||
.pdf-minimap-indicator {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
border-left: 3px solid var(--accent);
|
||||
pointer-events: none;
|
||||
transition: top 0.3s ease-out;
|
||||
}
|
||||
`;
|
||||
}
|
||||
Reference in New Issue
Block a user