mirror of
https://github.com/zadam/trilium.git
synced 2026-01-01 04:04:25 +01:00
feat(client/right_pane): display pages
This commit is contained in:
parent
77ad6950e8
commit
bcf72f4624
@ -25,6 +25,12 @@ export type GetTextEditorCallback = (editor: CKTextEditor) => void;
|
||||
|
||||
export interface NoteContextDataMap {
|
||||
toc: HeadingContext;
|
||||
pdfPages: {
|
||||
totalPages: number;
|
||||
currentPage: number;
|
||||
scrollToPage(page: number): void;
|
||||
requestThumbnail(page: number): void;
|
||||
};
|
||||
}
|
||||
|
||||
type ContextDataKey = keyof NoteContextDataMap;
|
||||
|
||||
56
apps/client/src/widgets/sidebar/PdfPages.css
Normal file
56
apps/client/src/widgets/sidebar/PdfPages.css
Normal file
@ -0,0 +1,56 @@
|
||||
.pdf-pages-list {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.pdf-page-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 8px;
|
||||
cursor: pointer;
|
||||
border: 2px solid transparent;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.pdf-page-item:hover {
|
||||
background-color: var(--hover-item-background-color);
|
||||
}
|
||||
|
||||
.pdf-page-item.active {
|
||||
border-color: var(--main-border-color);
|
||||
background-color: var(--active-item-background-color);
|
||||
}
|
||||
|
||||
.pdf-page-number {
|
||||
font-size: 12px;
|
||||
margin-bottom: 4px;
|
||||
color: var(--main-text-color);
|
||||
}
|
||||
|
||||
.pdf-page-thumbnail {
|
||||
width: 100%;
|
||||
height: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--accented-background-color);
|
||||
border: 1px solid var(--main-border-color);
|
||||
}
|
||||
|
||||
.pdf-page-thumbnail img {
|
||||
max-width: 100%;
|
||||
max-height: 100%;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
.pdf-page-loading {
|
||||
color: var(--muted-text-color);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.no-pages {
|
||||
padding: 16px;
|
||||
text-align: center;
|
||||
color: var(--muted-text-color);
|
||||
}
|
||||
109
apps/client/src/widgets/sidebar/PdfPages.tsx
Normal file
109
apps/client/src/widgets/sidebar/PdfPages.tsx
Normal file
@ -0,0 +1,109 @@
|
||||
import "./PdfPages.css";
|
||||
|
||||
import { useCallback, useEffect, useRef, useState } from "preact/hooks";
|
||||
|
||||
import { useActiveNoteContext, useGetContextData, useNoteProperty } from "../react/hooks";
|
||||
import RightPanelWidget from "./RightPanelWidget";
|
||||
|
||||
export default function PdfPages() {
|
||||
const { note } = useActiveNoteContext();
|
||||
const noteType = useNoteProperty(note, "type");
|
||||
const noteMime = useNoteProperty(note, "mime");
|
||||
|
||||
if (noteType !== "file" || noteMime !== "application/pdf") {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<RightPanelWidget id="pdf-pages" title="Pages" grow>
|
||||
<PdfPagesList />
|
||||
</RightPanelWidget>
|
||||
);
|
||||
}
|
||||
|
||||
function PdfPagesList() {
|
||||
const pagesData = useGetContextData("pdfPages");
|
||||
const [thumbnails, setThumbnails] = useState<Map<number, string>>(new Map());
|
||||
const requestedThumbnails = useRef<Set<number>>(new Set());
|
||||
|
||||
useEffect(() => {
|
||||
// Listen for thumbnail responses via custom event
|
||||
function handleThumbnail(event: CustomEvent) {
|
||||
const { pageNumber, dataUrl } = event.detail;
|
||||
console.log("[PdfPages] Received thumbnail for page:", pageNumber);
|
||||
setThumbnails(prev => new Map(prev).set(pageNumber, dataUrl));
|
||||
}
|
||||
|
||||
window.addEventListener("pdf-thumbnail", handleThumbnail as EventListener);
|
||||
return () => {
|
||||
window.removeEventListener("pdf-thumbnail", handleThumbnail as EventListener);
|
||||
};
|
||||
}, []);
|
||||
|
||||
const requestThumbnail = useCallback((pageNumber: number) => {
|
||||
// Only request if we haven't already requested it and don't have it
|
||||
if (!requestedThumbnails.current.has(pageNumber) && !thumbnails.has(pageNumber) && pagesData) {
|
||||
console.log("[PdfPages] Requesting thumbnail for page:", pageNumber);
|
||||
requestedThumbnails.current.add(pageNumber);
|
||||
pagesData.requestThumbnail(pageNumber);
|
||||
}
|
||||
}, [pagesData, thumbnails]);
|
||||
|
||||
if (!pagesData || pagesData.totalPages === 0) {
|
||||
return <div className="no-pages">No pages available</div>;
|
||||
}
|
||||
|
||||
const pages = Array.from({ length: pagesData.totalPages }, (_, i) => i + 1);
|
||||
|
||||
return (
|
||||
<div className="pdf-pages-list">
|
||||
{pages.map(pageNumber => (
|
||||
<PdfPageItem
|
||||
key={pageNumber}
|
||||
pageNumber={pageNumber}
|
||||
isActive={pageNumber === pagesData.currentPage}
|
||||
thumbnail={thumbnails.get(pageNumber)}
|
||||
onRequestThumbnail={requestThumbnail}
|
||||
onPageClick={() => pagesData.scrollToPage(pageNumber)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function PdfPageItem({
|
||||
pageNumber,
|
||||
isActive,
|
||||
thumbnail,
|
||||
onRequestThumbnail,
|
||||
onPageClick
|
||||
}: {
|
||||
pageNumber: number;
|
||||
isActive: boolean;
|
||||
thumbnail?: string;
|
||||
}) {
|
||||
const hasRequested = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (!thumbnail && !hasRequested.current) {
|
||||
hasRequested.current = true;
|
||||
onRequestThumbnail(pageNumber);
|
||||
}
|
||||
}, [pageNumber, thumbnail, onRequestThumbnail]);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`pdf-page-item ${isActive ? 'active' : ''}`}
|
||||
onClick={onPageClick}
|
||||
>
|
||||
<div className="pdf-page-number">{pageNumber}</div>
|
||||
<div className="pdf-page-thumbnail">
|
||||
{thumbnail ? (
|
||||
<img src={thumbnail} alt={`Page ${pageNumber}`} />
|
||||
) : (
|
||||
<div className="pdf-page-loading">Loading...</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@ -15,6 +15,7 @@ import { useActiveNoteContext, useLegacyWidget, useNoteProperty, useTriliumEvent
|
||||
import Icon from "../react/Icon";
|
||||
import LegacyRightPanelWidget from "../right_panel_widget";
|
||||
import HighlightsList from "./HighlightsList";
|
||||
import PdfPages from "./PdfPages";
|
||||
import RightPanelWidget from "./RightPanelWidget";
|
||||
import TableOfContents from "./TableOfContents";
|
||||
|
||||
@ -59,14 +60,17 @@ function useItems(rightPaneVisible: boolean, widgetsByParent: WidgetsByParent) {
|
||||
const noteType = useNoteProperty(note, "type");
|
||||
const noteMime = useNoteProperty(note, "mime");
|
||||
const [ highlightsList ] = useTriliumOptionJson<string[]>("highlightsList");
|
||||
const isPdf = noteType === "file" && noteMime === "application/pdf";
|
||||
|
||||
if (!rightPaneVisible) return [];
|
||||
const definitions: RightPanelWidgetDefinition[] = [
|
||||
{
|
||||
el: <TableOfContents />,
|
||||
enabled: (noteType === "text"
|
||||
|| noteType === "doc"
|
||||
|| (noteType === "file" && noteMime === "application/pdf")),
|
||||
enabled: (noteType === "text" || noteType === "doc" || isPdf),
|
||||
},
|
||||
{
|
||||
el: <PdfPages />,
|
||||
enabled: isPdf,
|
||||
},
|
||||
{
|
||||
el: <HighlightsList />,
|
||||
|
||||
@ -70,6 +70,46 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (event.data.type === "pdfjs-viewer-page-info") {
|
||||
console.log("[Pdf.tsx] Received page info:", event.data);
|
||||
noteContext.setContextData("pdfPages", {
|
||||
totalPages: event.data.totalPages,
|
||||
currentPage: event.data.currentPage,
|
||||
scrollToPage: (page: number) => {
|
||||
iframeRef.current?.contentWindow?.postMessage({
|
||||
type: "trilium-scroll-to-page",
|
||||
pageNumber: page
|
||||
}, "*");
|
||||
},
|
||||
requestThumbnail: (page: number) => {
|
||||
iframeRef.current?.contentWindow?.postMessage({
|
||||
type: "trilium-request-thumbnail",
|
||||
pageNumber: page
|
||||
}, "*");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (event.data.type === "pdfjs-viewer-current-page") {
|
||||
const currentPages = noteContext.getContextData("pdfPages");
|
||||
if (currentPages) {
|
||||
noteContext.setContextData("pdfPages", {
|
||||
...currentPages,
|
||||
currentPage: event.data.currentPage
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (event.data.type === "pdfjs-viewer-thumbnail") {
|
||||
// Forward thumbnail to any listeners
|
||||
window.dispatchEvent(new CustomEvent("pdf-thumbnail", {
|
||||
detail: {
|
||||
pageNumber: event.data.pageNumber,
|
||||
dataUrl: event.data.dataUrl
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener("message", handleMessage);
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import interceptPersistence from "./persistence";
|
||||
import { extractAndSendToc, setupScrollToHeading, setupActiveHeadingTracking } from "./toc";
|
||||
import { setupPdfPages } from "./pages";
|
||||
|
||||
const LOG_EVENT_BUS = false;
|
||||
|
||||
@ -21,6 +22,7 @@ async function main() {
|
||||
extractAndSendToc();
|
||||
setupScrollToHeading();
|
||||
setupActiveHeadingTracking();
|
||||
setupPdfPages();
|
||||
});
|
||||
await app.initializedPromise;
|
||||
};
|
||||
|
||||
93
packages/pdfjs-viewer/src/pages.ts
Normal file
93
packages/pdfjs-viewer/src/pages.ts
Normal file
@ -0,0 +1,93 @@
|
||||
export function setupPdfPages() {
|
||||
const app = window.PDFViewerApplication;
|
||||
|
||||
// Send initial page info when pages are initialized
|
||||
app.eventBus.on("pagesinit", () => {
|
||||
sendPageInfo();
|
||||
});
|
||||
|
||||
// Also send immediately if document is already loaded
|
||||
if (app.pdfDocument && app.pdfViewer) {
|
||||
sendPageInfo();
|
||||
}
|
||||
|
||||
// Track current page changes
|
||||
app.eventBus.on("pagechanging", (evt: any) => {
|
||||
window.parent.postMessage({
|
||||
type: "pdfjs-viewer-current-page",
|
||||
currentPage: evt.pageNumber
|
||||
}, "*");
|
||||
});
|
||||
|
||||
// Listen for scroll-to-page requests
|
||||
window.addEventListener("message", (event) => {
|
||||
if (event.data?.type === "trilium-scroll-to-page") {
|
||||
const pageNumber = event.data.pageNumber;
|
||||
app.pdfViewer.currentPageNumber = pageNumber;
|
||||
}
|
||||
});
|
||||
|
||||
// Listen for thumbnail requests
|
||||
window.addEventListener("message", async (event) => {
|
||||
if (event.data?.type === "trilium-request-thumbnail") {
|
||||
const pageNumber = event.data.pageNumber;
|
||||
console.log("[PDF Pages] Received thumbnail request for page:", pageNumber);
|
||||
await generateThumbnail(pageNumber);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function sendPageInfo() {
|
||||
const app = window.PDFViewerApplication;
|
||||
|
||||
console.log("[PDF Pages] Sending page info:", {
|
||||
totalPages: app.pdfDocument?.numPages,
|
||||
currentPage: app.pdfViewer?.currentPageNumber
|
||||
});
|
||||
|
||||
window.parent.postMessage({
|
||||
type: "pdfjs-viewer-page-info",
|
||||
totalPages: app.pdfDocument.numPages,
|
||||
currentPage: app.pdfViewer.currentPageNumber
|
||||
}, "*");
|
||||
}
|
||||
|
||||
async function generateThumbnail(pageNumber: number) {
|
||||
const app = window.PDFViewerApplication;
|
||||
|
||||
console.log("[PDF Pages] Generating thumbnail for page:", pageNumber);
|
||||
|
||||
try {
|
||||
const page = await app.pdfDocument.getPage(pageNumber);
|
||||
|
||||
// Create canvas for thumbnail
|
||||
const canvas = document.createElement('canvas');
|
||||
const context = canvas.getContext('2d');
|
||||
if (!context) return;
|
||||
|
||||
// Set thumbnail size (smaller than actual page)
|
||||
const viewport = page.getViewport({ scale: 0.2 });
|
||||
canvas.width = viewport.width;
|
||||
canvas.height = viewport.height;
|
||||
|
||||
// Render page to canvas
|
||||
await page.render({
|
||||
canvasContext: context,
|
||||
viewport: viewport
|
||||
}).promise;
|
||||
|
||||
// Convert to data URL
|
||||
const dataUrl = canvas.toDataURL('image/jpeg', 0.7);
|
||||
|
||||
console.log("[PDF Pages] Sending thumbnail for page:", pageNumber, "size:", dataUrl.length);
|
||||
|
||||
// Send thumbnail to parent
|
||||
window.parent.postMessage({
|
||||
type: "pdfjs-viewer-thumbnail",
|
||||
pageNumber,
|
||||
dataUrl
|
||||
}, "*");
|
||||
} catch (error) {
|
||||
console.error(`Error generating thumbnail for page ${pageNumber}:`, error);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user