feat(client/right_pane): jump to heading

This commit is contained in:
Elian Doran 2025-12-29 21:55:47 +02:00
parent b6506a9331
commit 64ca04ad07
No known key found for this signature in database
3 changed files with 92 additions and 29 deletions

View File

@ -22,7 +22,7 @@ interface HeadingsWithNesting extends RawHeading {
}
export interface HeadingContext {
// scrollToHeading(heading: RawHeading): void;
scrollToHeading(heading: RawHeading): void;
headings: RawHeading[];
}
@ -43,14 +43,11 @@ export default function TableOfContents() {
function PdfTableOfContents() {
const data = useGetContextData("toc");
console.log("Rendering with data", data);
return (
<AbstractTableOfContents
headings={data?.headings || []}
scrollToHeading={heading => {
}}
scrollToHeading={data?.scrollToHeading || (() => {})}
/>
);
}

View File

@ -6,7 +6,7 @@ import FBlob from "../../../entities/fblob";
import FNote from "../../../entities/fnote";
import server from "../../../services/server";
import { useViewModeConfig } from "../../collections/NoteList";
import { useSetContextData, useTriliumOption } from "../../react/hooks";
import { useTriliumOption } from "../../react/hooks";
const VARIABLE_WHITELIST = new Set([
"root-background",
@ -40,12 +40,22 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: {
if (event.data.type === "pdfjs-viewer-toc") {
if (event.data.data) {
// Convert PDF outline to HeadingContext format
const headings = convertPdfOutlineToHeadings(event.data.data);
noteContext.setContextData("toc", {
headings: convertPdfOutlineToHeadings(event.data.data)
headings,
scrollToHeading: (heading) => {
iframeRef.current?.contentWindow?.postMessage({
type: "trilium-scroll-to-heading",
headingId: heading.id
}, "*");
}
});
} else {
// No ToC available, use empty headings
noteContext.setContextData("toc", { headings: [] });
noteContext.setContextData("toc", {
headings: [],
scrollToHeading: () => {}
});
}
}
}
@ -135,25 +145,36 @@ function cssVarsToString(vars) {
interface PdfOutlineItem {
title: string;
level: number;
dest: any;
dest: unknown;
id: string;
items: PdfOutlineItem[];
}
function convertPdfOutlineToHeadings(outline: PdfOutlineItem[], parentLevel = 0) {
const headings: any[] = [];
interface PdfHeading {
level: number;
text: string;
id: string;
element: null;
}
for (const item of outline) {
headings.push({
level: parentLevel + 1,
text: item.title,
id: `pdf-outline-${headings.length}`,
element: null // PDFs don't have DOM elements
});
function convertPdfOutlineToHeadings(outline: PdfOutlineItem[]): PdfHeading[] {
const headings: PdfHeading[] = [];
if (item.items && item.items.length > 0) {
headings.push(...convertPdfOutlineToHeadings(item.items, parentLevel + 1));
function flatten(items: PdfOutlineItem[]) {
for (const item of items) {
headings.push({
level: item.level + 1,
text: item.title,
id: item.id,
element: null // PDFs don't have DOM elements
});
if (item.items && item.items.length > 0) {
flatten(item.items);
}
}
}
flatten(outline);
return headings;
}

View File

@ -18,6 +18,7 @@ async function main() {
app.eventBus.on("documentloaded", () => {
manageSave();
extractAndSendToc();
setupScrollToHeading();
});
await app.initializedPromise;
};
@ -91,27 +92,71 @@ async function extractAndSendToc() {
return;
}
// Convert PDF.js outline format to a simpler structure
const toc = convertOutlineToToc(outline);
// Store outline items with their destinations for later scrolling
const outlineMap = new Map();
const toc = convertOutlineToToc(outline, 0, outlineMap);
// Store the map globally so setupScrollToHeading can access it
(window as any).TRILIUM_OUTLINE_MAP = outlineMap;
window.parent.postMessage({
type: "pdfjs-viewer-toc",
data: toc
}, "*");
} catch (error) {
window.parent.postMessage({
type: "pdfjs-viewer-toc",
data: null
}, "*");
}
}
function convertOutlineToToc(outline: any[], level = 0): any[] {
return outline.map(item => ({
title: item.title,
level: level,
dest: item.dest,
items: item.items && item.items.length > 0 ? convertOutlineToToc(item.items, level + 1) : []
}));
function convertOutlineToToc(outline: any[], level = 0, outlineMap?: Map<string, any>, parentId = ""): any[] {
return outline.map((item, index) => {
const id = parentId ? `${parentId}-${index}` : `pdf-outline-${index}`;
if (outlineMap) {
outlineMap.set(id, item);
}
return {
title: item.title,
level: level,
dest: item.dest,
id: id,
items: item.items && item.items.length > 0 ? convertOutlineToToc(item.items, level + 1, outlineMap, id) : []
};
});
}
main();
console.log("Custom script loaded");
function setupScrollToHeading() {
window.addEventListener("message", async (event) => {
if (event.data?.type === "trilium-scroll-to-heading") {
const headingId = event.data.headingId;
const outlineMap = (window as any).TRILIUM_OUTLINE_MAP as Map<string, any>;
if (!outlineMap) return;
const outlineItem = outlineMap.get(headingId);
if (!outlineItem || !outlineItem.dest) return;
const app = window.PDFViewerApplication;
// Navigate to the destination
try {
const dest = typeof outlineItem.dest === 'string'
? await app.pdfDocument.getDestination(outlineItem.dest)
: outlineItem.dest;
if (dest) {
app.pdfLinkService.goToDestination(dest);
}
} catch (error) {
console.error("Error navigating to heading:", error);
}
}
});
}