chore(pdf): process PDF outline

This commit is contained in:
Elian Doran 2025-12-29 21:44:49 +02:00
parent e36049cd43
commit fd7222242a
No known key found for this signature in database
3 changed files with 51 additions and 10 deletions

View File

@ -12,6 +12,7 @@ import server from "../services/server.js";
import treeService from "../services/tree.js";
import utils from "../services/utils.js";
import { ReactWrappedWidget } from "../widgets/basic_widget.js";
import type { HeadingContext } from "../widgets/sidebar/TableOfContents.js";
import appContext, { type EventData, type EventListener } from "./app_context.js";
import Component from "./component.js";
@ -22,6 +23,12 @@ export interface SetNoteOpts {
export type GetTextEditorCallback = (editor: CKTextEditor) => void;
export interface NoteContextDataMap {
toc: HeadingContext;
}
type ContextDataKey = keyof NoteContextDataMap;
class NoteContext extends Component implements EventListener<"entitiesReloaded"> {
ntxId: string | null;
hoistedNoteId: string;
@ -469,8 +476,9 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
* @param key - Unique identifier for the data type (e.g., "toc", "pdfPages", "codeOutline")
* @param value - The data to store (will be cleared when switching notes)
*/
setContextData<T>(key: string, value: T): void {
setContextData<K extends ContextDataKey>(key: K, value: NoteContextDataMap[K]): void {
this.contextData.set(key, value);
console.trace("Set context data", key, value);
// Trigger event so subscribers can react
this.triggerEvent("contextDataChanged", {
noteContext: this,
@ -485,21 +493,21 @@ class NoteContext extends Component implements EventListener<"entitiesReloaded">
* @param key - The data key to retrieve
* @returns The stored data, or undefined if not found
*/
getContextData<T>(key: string): T | undefined {
return this.contextData.get(key) as T | undefined;
getContextData<K extends ContextDataKey>(key: K): NoteContextDataMap[K] | undefined {
return this.contextData.get(key) as NoteContextDataMap[K] | undefined;
}
/**
* Check if context data exists for a given key.
*/
hasContextData(key: string): boolean {
hasContextData(key: ContextDataKey): boolean {
return this.contextData.has(key);
}
/**
* Clear specific context data.
*/
clearContextData(key: string): void {
clearContextData(key: ContextDataKey): void {
this.contextData.delete(key);
this.triggerEvent("contextDataChanged", {
noteContext: this,

View File

@ -21,6 +21,11 @@ interface HeadingsWithNesting extends RawHeading {
children: HeadingsWithNesting[];
}
export interface HeadingContext {
// scrollToHeading(heading: RawHeading): void;
headings: RawHeading[];
}
export default function TableOfContents() {
const { note, noteContext } = useActiveNoteContext();
const noteType = useNoteProperty(note, "type");
@ -38,6 +43,7 @@ export default function TableOfContents() {
function PdfTableOfContents() {
const data = useGetContextData("toc");
console.log("Rendering with data", data);
return (
<pre>{JSON.stringify(data, null, 2)}</pre>

View File

@ -39,10 +39,13 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: {
if (event.data.type === "pdfjs-viewer-toc") {
if (event.data.data) {
noteContext.setContextData("toc", event.data.data);
// Convert PDF outline to HeadingContext format
noteContext.setContextData("toc", {
headings: convertPdfOutlineToHeadings(event.data.data)
});
} else {
// No ToC available, fallback to note title
noteContext.setContextData("toc", note.title);
// No ToC available, use empty headings
noteContext.setContextData("toc", { headings: [] });
}
}
}
@ -60,8 +63,6 @@ export default function PdfPreview({ note, blob, componentId, noteContext }: {
}
}, [ blob ]);
// Initial ToC is set to note.title, will be replaced by actual ToC when received
useSetContextData(noteContext, "toc", note.title);
return (historyConfig &&
<iframe
@ -130,3 +131,29 @@ function cssVarsToString(vars) {
.map(([k, v]) => ` ${k}: ${v};`)
.join('\n')}\n}`;
}
interface PdfOutlineItem {
title: string;
level: number;
dest: any;
items: PdfOutlineItem[];
}
function convertPdfOutlineToHeadings(outline: PdfOutlineItem[], parentLevel = 0) {
const headings: any[] = [];
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
});
if (item.items && item.items.length > 0) {
headings.push(...convertPdfOutlineToHeadings(item.items, parentLevel + 1));
}
}
return headings;
}