@@ -60,12 +60,7 @@ function findTextScrollPosition(startOffset: number, endOffset: number): number
6060}
6161
6262/**
63- * Tracks which panel is currently being scrolled by the user
64- */
65- type ScrollSource = "pdf" | "sidebar" | "programmatic" | null
66-
67- /**
68- * Set up bidirectional scroll synchronization between PDF and annotations
63+ * Set up click-based navigation between PDF and annotations
6964 */
7065export function setupScrollSync ( viewer : Element ) : void {
7166 const pdfContainer = viewer . querySelector ( ".annotation-pdf-container" ) as HTMLElement
@@ -100,166 +95,8 @@ export function setupScrollSync(viewer: Element): void {
10095 }
10196 } )
10297
103- // State management for scroll synchronization
104- let scrollSource : ScrollSource = null
105- let programmaticScrollTarget : HTMLElement | null = null
106- let debounceTimeout : NodeJS . Timeout | null = null
107- let resetSourceTimeout : NodeJS . Timeout | null = null
108-
109- /**
110- * Set the scroll source and schedule reset
111- */
112- function setScrollSource ( source : ScrollSource , duration : number = 600 ) : void {
113- scrollSource = source
114-
115- if ( resetSourceTimeout ) clearTimeout ( resetSourceTimeout )
116-
117- if ( source !== null ) {
118- resetSourceTimeout = setTimeout ( ( ) => {
119- scrollSource = null
120- programmaticScrollTarget = null
121- } , duration )
122- }
123- }
124-
125- /**
126- * Detect if a scroll event is user-initiated on a specific element
127- */
128- function isUserScroll ( element : HTMLElement ) : boolean {
129- // If we're in programmatic mode and this is the target, it's not user scroll
130- if ( scrollSource === "programmatic" && programmaticScrollTarget === element ) {
131- return false
132- }
133-
134- // If scroll source is already set to the other panel, this is programmatic
135- if ( scrollSource === "pdf" && element === sidebar ) return false
136- if ( scrollSource === "sidebar" && element === pdfContainer ) return false
137-
138- // Otherwise, it's a user scroll
139- return true
140- }
141-
142- // Track user interaction to detect scroll intent
143- let pdfUserInteracting = false
144- let sidebarUserInteracting = false
145-
146- pdfContainer . addEventListener ( "mouseenter" , ( ) => { pdfUserInteracting = true } )
147- pdfContainer . addEventListener ( "mouseleave" , ( ) => { pdfUserInteracting = false } )
148- sidebar . addEventListener ( "mouseenter" , ( ) => { sidebarUserInteracting = true } )
149- sidebar . addEventListener ( "mouseleave" , ( ) => { sidebarUserInteracting = false } )
150-
151- // Debounced scroll handlers
152- const onPdfScroll = ( ) => {
153- if ( ! isUserScroll ( pdfContainer ) ) return
154-
155- // Set source if this is user-initiated
156- if ( pdfUserInteracting && scrollSource !== "pdf" ) {
157- setScrollSource ( "pdf" )
158- }
159-
160- // Clear existing timeout
161- if ( debounceTimeout ) clearTimeout ( debounceTimeout )
162-
163- // Debounce to wait for scroll to settle
164- debounceTimeout = setTimeout ( ( ) => {
165- if ( scrollSource !== "pdf" ) return
166-
167- const scrollTop = pdfContainer . scrollTop
168- const scrollCenter = scrollTop + pdfContainer . clientHeight / 2
169-
170- let closestAnnotation = null
171- let minDistance = Infinity
172-
173- annotationItems . forEach ( ( ann ) => {
174- const pdfY = parseFloat ( ann . getAttribute ( "data-pdf-y" ) || "0" )
175- if ( pdfY > 0 ) {
176- const distance = Math . abs ( pdfY - scrollCenter )
177- if ( distance < minDistance ) {
178- minDistance = distance
179- closestAnnotation = ann
180- }
181- }
182- } )
183-
184- if ( closestAnnotation ) {
185- syncToAnnotation ( closestAnnotation )
186- }
187- } , 150 )
188- }
189-
190- // When annotations scroll, find closest annotation and center its highlight in PDF
191- const onAnnotationScroll = ( ) => {
192- if ( ! isUserScroll ( sidebar ) ) return
193-
194- // Set source if this is user-initiated
195- if ( sidebarUserInteracting && scrollSource !== "sidebar" ) {
196- setScrollSource ( "sidebar" )
197- }
198-
199- // Clear existing timeout
200- if ( debounceTimeout ) clearTimeout ( debounceTimeout )
201-
202- // Debounce to wait for scroll to settle
203- debounceTimeout = setTimeout ( ( ) => {
204- if ( scrollSource !== "sidebar" ) return
205-
206- const scrollTop = sidebar . scrollTop
207- const scrollCenter = scrollTop + sidebar . clientHeight / 2
208-
209- let closestAnnotation : HTMLElement | null = null
210- let minDistance = Infinity
211-
212- annotationItems . forEach ( ( ann ) => {
213- const annotationTop = ann . offsetTop
214- const annotationCenter = annotationTop + ann . clientHeight / 2
215- const distance = Math . abs ( annotationCenter - scrollCenter )
216-
217- if ( distance < minDistance ) {
218- minDistance = distance
219- closestAnnotation = ann
220- }
221- } )
222-
223- if ( closestAnnotation ) {
224- syncToPDF ( closestAnnotation )
225- }
226- } , 150 )
227- }
228-
22998 /**
230- * Sync sidebar to match PDF scroll position
231- */
232- function syncToAnnotation ( activeAnnotation : HTMLElement ) : void {
233- updateActiveAnnotation ( activeAnnotation )
234-
235- // Scroll annotation into center view
236- setScrollSource ( "programmatic" , 600 )
237- programmaticScrollTarget = sidebar
238-
239- const annotationTop = activeAnnotation . offsetTop
240- const targetScroll =
241- annotationTop - sidebar . clientHeight / 2 + activeAnnotation . clientHeight / 2
242- sidebar . scrollTo ( { top : targetScroll , behavior : "smooth" } )
243- }
244-
245- /**
246- * Sync PDF to match sidebar scroll position
247- */
248- function syncToPDF ( activeAnnotation : HTMLElement ) : void {
249- updateActiveAnnotation ( activeAnnotation )
250-
251- const pdfY = parseFloat ( activeAnnotation . getAttribute ( "data-pdf-y" ) || "0" )
252- if ( pdfY > 0 ) {
253- setScrollSource ( "programmatic" , 600 )
254- programmaticScrollTarget = pdfContainer
255-
256- const targetScroll = pdfY - pdfContainer . clientHeight / 2
257- pdfContainer . scrollTo ( { top : targetScroll , behavior : "smooth" } )
258- }
259- }
260-
261- /**
262- * Update which annotation is marked as active and render highlights
99+ * Update which annotation is marked as active and highlight accordingly
263100 */
264101 function updateActiveAnnotation ( activeAnnotation : HTMLElement ) : void {
265102 // Mark as active
@@ -271,34 +108,56 @@ export function setupScrollSync(viewer: Element): void {
271108 }
272109 } )
273110
274- // Render highlight for active annotation
111+ // Update active highlights
275112 const annotationId = activeAnnotation . getAttribute ( "data-annotation-id" )
276- const annotationData = window . annotationsData ?. find ( ( a ) => a . id === annotationId )
277- if ( annotationData ) {
278- window . renderHighlights ( annotationData )
113+ if ( annotationId && window . setActiveHighlight ) {
114+ window . setActiveHighlight ( annotationId )
279115 }
280116 }
281117
282- pdfContainer . addEventListener ( "scroll" , onPdfScroll )
283- sidebar . addEventListener ( "scroll" , onAnnotationScroll )
284-
285118 // Click annotation to scroll to its position in PDF
286119 annotationItems . forEach ( ( annotation ) => {
287120 annotation . addEventListener ( "click" , ( ) => {
288121 const pdfY = parseFloat ( annotation . getAttribute ( "data-pdf-y" ) || "0" )
289122 if ( pdfY > 0 ) {
290- setScrollSource ( "programmatic" , 600 )
291- programmaticScrollTarget = pdfContainer
292-
293- const scrollTarget = pdfY - pdfContainer . clientHeight / 2
123+ // Scroll to top of highlight with small offset for better visibility
124+ const scrollTarget = pdfY - 16
294125 pdfContainer . scrollTo ( { top : scrollTarget , behavior : "smooth" } )
295126
296127 updateActiveAnnotation ( annotation )
297128 }
298129 } )
299130 } )
300131
301- // Initial update
302- setScrollSource ( "pdf" , 100 )
303- setTimeout ( ( ) => onPdfScroll ( ) , 50 )
132+ // Click highlight to scroll to its annotation in sidebar
133+ // Use event delegation on the PDF container since highlights are created dynamically
134+ pdfContainer . addEventListener ( "click" , ( event ) => {
135+ const target = event . target as HTMLElement
136+ if ( target . classList . contains ( "pdf-text-highlight" ) ) {
137+ const annotationId = target . getAttribute ( "data-annotation-id" )
138+ const annotation = annotationItems . find (
139+ ( ann ) => ann . getAttribute ( "data-annotation-id" ) === annotationId ,
140+ )
141+
142+ if ( annotation ) {
143+ const annotationTop = annotation . offsetTop
144+ // Scroll to top of annotation with small offset for better visibility
145+ const targetScroll = annotationTop - 16
146+ sidebar . scrollTo ( { top : targetScroll , behavior : "smooth" } )
147+
148+ updateActiveAnnotation ( annotation )
149+ }
150+ }
151+ } )
152+
153+ // Render all highlights initially (after text layers are ready)
154+ // Use setTimeout to ensure functions are exposed on window
155+ setTimeout ( ( ) => {
156+ if ( window . renderAllHighlights ) {
157+ console . log ( "[ScrollSync] Rendering all highlights" )
158+ window . renderAllHighlights ( )
159+ } else {
160+ console . warn ( "[ScrollSync] window.renderAllHighlights not available" )
161+ }
162+ } , 100 )
304163}
0 commit comments