@@ -3,7 +3,14 @@ import { useState, useEffect } from 'preact/hooks';
33import { useBookmarkCapability } from '@embedpdf/plugin-bookmark/preact' ;
44import { useScrollCapability } from '@embedpdf/plugin-scroll/preact' ;
55import { useTranslations } from '@embedpdf/plugin-i18n/preact' ;
6- import { PdfBookmarkObject , PdfZoomMode , PdfErrorCode , ignore } from '@embedpdf/models' ;
6+ import {
7+ PdfBookmarkObject ,
8+ PdfZoomMode ,
9+ PdfErrorCode ,
10+ ignore ,
11+ PdfActionType ,
12+ PdfDestinationObject ,
13+ } from '@embedpdf/models' ;
714import { useDocumentState } from '@embedpdf/core/preact' ;
815import { Icon } from './ui/icon' ;
916import { ChevronDownIcon } from './icons/chevron-down' ;
@@ -20,16 +27,25 @@ export function OutlineSidebar({ documentId }: OutlineSidebarProps) {
2027 const documentState = useDocumentState ( documentId ) ;
2128 const [ bookmarks , setBookmarks ] = useState < PdfBookmarkObject [ ] > ( [ ] ) ;
2229 const [ expandedItems , setExpandedItems ] = useState < Set < string > > ( new Set ( ) ) ;
30+ const [ isLoading , setIsLoading ] = useState ( true ) ;
2331
2432 useEffect ( ( ) => {
2533 if ( ! bookmark || ! documentState ?. document ) return ;
34+
35+ setIsLoading ( true ) ;
2636 const task = bookmark . getBookmarks ( ) ;
27- task . wait ( ( { bookmarks } ) => {
28- setBookmarks ( bookmarks ) ;
29- // Auto-expand first level items
30- const firstLevelIds = bookmarks . map ( ( _ , index ) => `bookmark-${ index } ` ) ;
31- setExpandedItems ( new Set ( firstLevelIds ) ) ;
32- } , ignore ) ;
37+ task . wait (
38+ ( { bookmarks } ) => {
39+ setBookmarks ( bookmarks ) ;
40+ // Auto-expand first level items
41+ const firstLevelIds = bookmarks . map ( ( _ , index ) => `bookmark-${ index } ` ) ;
42+ setExpandedItems ( new Set ( firstLevelIds ) ) ;
43+ setIsLoading ( false ) ;
44+ } ,
45+ ( ) => {
46+ setIsLoading ( false ) ;
47+ } ,
48+ ) ;
3349
3450 return ( ) => {
3551 task . abort ( {
@@ -40,33 +56,47 @@ export function OutlineSidebar({ documentId }: OutlineSidebarProps) {
4056 } , [ bookmark , documentState ?. document ] ) ;
4157
4258 const handleBookmarkClick = ( bookmark : PdfBookmarkObject ) => {
43- if ( ! scroll || ! bookmark . target || bookmark . target . type !== 'action' ) return ;
44-
45- const action = bookmark . target . action ;
46- if ( action . type === 1 && action . destination ) {
47- // Type 1 is "Go to destination"
48- const destination = action . destination ;
49-
50- if ( destination . zoom . mode === PdfZoomMode . XYZ ) {
51- const page = documentState ?. document ?. pages . find ( ( p ) => p . index === destination . pageIndex ) ;
52- if ( ! page ) return ;
53-
54- scroll . scrollToPage ( {
55- pageNumber : destination . pageIndex + 1 ,
56- pageCoordinates : destination . zoom . params
57- ? {
58- x : destination . zoom . params . x ,
59- y : page . size . height - destination . zoom . params . y ,
60- }
61- : undefined ,
62- behavior : 'smooth' ,
63- } ) ;
64- } else if ( destination . zoom . mode === PdfZoomMode . FitPage ) {
65- scroll . scrollToPage ( {
66- pageNumber : destination . pageIndex + 1 ,
67- behavior : 'smooth' ,
68- } ) ;
59+ if ( ! scroll || ! bookmark . target ) return ;
60+
61+ // Extract destination from either action or direct destination target
62+ let destination : PdfDestinationObject | undefined ;
63+
64+ if ( bookmark . target . type === 'action' ) {
65+ const action = bookmark . target . action ;
66+ if ( action . type === PdfActionType . Goto || action . type === PdfActionType . RemoteGoto ) {
67+ destination = action . destination ;
68+ } else if ( action . type === PdfActionType . URI ) {
69+ // Open URI in new tab
70+ window . open ( action . uri , '_blank' ) ;
71+ return ;
6972 }
73+ // Other action types (Unsupported, LaunchAppOrOpenFile) are not handled
74+ } else if ( bookmark . target . type === 'destination' ) {
75+ destination = bookmark . target . destination ;
76+ }
77+
78+ if ( ! destination ) return ;
79+
80+ if ( destination . zoom . mode === PdfZoomMode . XYZ ) {
81+ const page = documentState ?. document ?. pages . find ( ( p ) => p . index === destination . pageIndex ) ;
82+ if ( ! page ) return ;
83+
84+ scroll . scrollToPage ( {
85+ pageNumber : destination . pageIndex + 1 ,
86+ pageCoordinates : destination . zoom . params
87+ ? {
88+ x : destination . zoom . params . x ,
89+ y : page . size . height - destination . zoom . params . y ,
90+ }
91+ : undefined ,
92+ behavior : 'smooth' ,
93+ } ) ;
94+ } else {
95+ // Handle FitPage, FitH, FitV, FitR, FitB, FitBH, FitBV, etc.
96+ scroll . scrollToPage ( {
97+ pageNumber : destination . pageIndex + 1 ,
98+ behavior : 'smooth' ,
99+ } ) ;
70100 }
71101 } ;
72102
@@ -127,7 +157,7 @@ export function OutlineSidebar({ documentId }: OutlineSidebarProps) {
127157 ) ;
128158 } ;
129159
130- if ( ! documentState ?. document ) {
160+ if ( ! documentState ?. document || isLoading ) {
131161 return (
132162 < div className = "text-fg-secondary flex h-full flex-col gap-3 p-4 text-sm" >
133163 < div className = "text-fg-primary font-medium" > { translate ( 'outline.title' ) } </ div >
0 commit comments