@@ -50,6 +50,10 @@ const DocumentationPage = GObject.registerClass(
5050 class DocumentationPage extends GObject . Object { } ,
5151) ;
5252
53+ const URI_TO_SIDEBAR_PATH = { } ;
54+ let sync_sidebar = false ;
55+ let scrolled_to = false ;
56+
5357export default function DocumentationViewer ( { application } ) {
5458 const builder = Gtk . Builder . new_from_resource ( resource ) ;
5559
@@ -112,10 +116,27 @@ export default function DocumentationViewer({ application }) {
112116 ) ;
113117 user_content_manager . add_style_sheet ( stylesheet ) ;
114118
119+ const root_model = Gio . ListStore . new ( DocumentationPage ) ;
120+ const browse_selection_model = createBrowseSelectionModel (
121+ root_model ,
122+ webview ,
123+ ) ;
124+ browse_list_view . model = browse_selection_model ;
125+
115126 webview . connect ( "load-changed" , ( ) => {
116127 updateButtons ( ) ;
117128 } ) ;
118129
130+ webview . connect ( "notify::uri" , ( ) => {
131+ const selected_item = browse_selection_model . selected_item . item ;
132+ if ( webview . uri !== selected_item . uri ) {
133+ sync_sidebar = true ;
134+ const path = URI_TO_SIDEBAR_PATH [ webview . uri ] ;
135+ if ( ! path ) return ;
136+ selectSidebarItem ( browse_list_view , path ) ;
137+ }
138+ } ) ;
139+
119140 webview . get_back_forward_list ( ) . connect ( "changed" , ( ) => {
120141 updateButtons ( ) ;
121142 } ) ;
@@ -133,6 +154,23 @@ export default function DocumentationViewer({ application }) {
133154 webview . go_forward ( ) ;
134155 } ) ;
135156
157+ const adj = browse_page . get_vscrollbar ( ) . adjustment ;
158+ adj . connect ( "value-changed" , ( ) => {
159+ if ( scrolled_to ) {
160+ const index = browse_selection_model . selected ;
161+ const adj = browse_page . get_vscrollbar ( ) . adjustment ;
162+ const bottom_edge = ( index + 1 ) * 38 - adj . value ;
163+ const top_edge = bottom_edge - 38 ;
164+ // If row is not visible after scroll_to, adjust
165+ if ( bottom_edge === 0 ) {
166+ adj . value -= 38 ;
167+ } else if ( top_edge === adj . page_size ) {
168+ adj . value += 38 ;
169+ }
170+ scrolled_to = false ;
171+ }
172+ } ) ;
173+
136174 const expr = new Gtk . ClosureExpression (
137175 GObject . TYPE_STRING ,
138176 ( item ) => item . search_name ,
@@ -158,11 +196,10 @@ export default function DocumentationViewer({ application }) {
158196 sorter . expression = expr ;
159197 search_model . connect ( "selection-changed" , ( ) => {
160198 const uri = search_model . selected_item . uri ;
161- webview . load_uri ( uri ) ;
199+ const sidebar_path = URI_TO_SIDEBAR_PATH [ uri ] ;
200+ selectSidebarItem ( browse_list_view , sidebar_path ) ;
162201 } ) ;
163202
164- const root_model = Gio . ListStore . new ( DocumentationPage ) ;
165- browse_list_view . model = createBrowseSelectionModel ( root_model , webview ) ;
166203 let promise_load ;
167204 async function load ( ) {
168205 if ( ! promise_load ) {
@@ -198,7 +235,8 @@ export default function DocumentationViewer({ application }) {
198235 load ( )
199236 . then ( ( ) => {
200237 if ( ! mapped ) {
201- browse_list_view . model . selected = 12 ;
238+ collapseAllRows ( browse_selection_model . model ) ;
239+ browse_selection_model . selected = 12 ;
202240 search_entry . text = "" ;
203241 onSearchChanged ( ) ;
204242 }
@@ -209,6 +247,33 @@ export default function DocumentationViewer({ application }) {
209247 application . set_accels_for_action ( "app.documentation" , [ "<Control>M" ] ) ;
210248}
211249
250+ function sortFunc ( doc1 , doc2 ) {
251+ return doc1 . name . localeCompare ( doc2 . name ) ;
252+ }
253+
254+ function collapseAllRows ( model ) {
255+ for ( let i = 0 ; i < model . n_items ; i ++ ) {
256+ const row = model . get_row ( i ) ;
257+ row . expanded = false ;
258+ }
259+ }
260+
261+ function selectSidebarItem ( browse_list_view , path ) {
262+ const selection_model = browse_list_view . model ;
263+ collapseAllRows ( selection_model . model ) ;
264+ for ( const index of path . slice ( 0 , - 1 ) ) {
265+ const row = selection_model . model . get_row ( index ) ;
266+ row . expanded = true ;
267+ }
268+ const index = path [ path . length - 1 ] ;
269+ // If possible, overshoot scrolling by one row to ensure selected row is visible
270+ index + 1 === selection_model . n_items
271+ ? browse_list_view . scroll_to ( index , Gtk . ListScrollFlags . NONE , null )
272+ : browse_list_view . scroll_to ( index + 1 , Gtk . ListScrollFlags . NONE , null ) ;
273+ selection_model . selected = index ;
274+ scrolled_to = true ;
275+ }
276+
212277async function loadLibrary ( model , directory ) {
213278 try {
214279 const json_file = directory . get_child ( "index.json" ) ;
@@ -225,7 +290,7 @@ async function loadLibrary(model, directory) {
225290 children : getChildren ( index , directory ) ,
226291 } ) ;
227292
228- model . append ( page ) ;
293+ model . insert_sorted ( page , sortFunc ) ;
229294 } catch ( error ) {
230295 if ( ! error . matches ( Gio . IOErrorEnum , Gio . IOErrorEnum . NOT_FOUND ) ) throw error ;
231296 }
@@ -267,10 +332,21 @@ async function scanLibraries(model, base_dir) {
267332 return Promise . allSettled ( libraries ) ;
268333}
269334
270- function flattenModel ( list_store , flattened_model = newListStore ( ) ) {
335+ function flattenModel (
336+ list_store ,
337+ flattened_model = newListStore ( ) ,
338+ path = [ 0 ] ,
339+ ) {
271340 for ( const item of list_store ) {
272341 if ( item . search_name ) flattened_model . append ( item ) ;
273- if ( item . children ) flattenModel ( item . children , flattened_model ) ;
342+ if ( item . children ) {
343+ flattenModel ( item . children , flattened_model , [
344+ ...path ,
345+ path [ path . length - 1 ] + 1 ,
346+ ] ) ;
347+ }
348+ URI_TO_SIDEBAR_PATH [ item . uri ] = path . slice ( ) ;
349+ path [ path . length - 1 ] ++ ;
274350 }
275351 return flattened_model ;
276352}
@@ -282,16 +358,13 @@ function createBrowseSelectionModel(root_model, webview) {
282358 false ,
283359 ( item ) => item . children ,
284360 ) ;
285- const sorter = Gtk . TreeListRowSorter . new (
286- Gtk . CustomSorter . new ( ( a , b ) => {
287- const name1 = a . name ;
288- const name2 = b . name ;
289- return name1 . localeCompare ( name2 ) ;
290- } ) ,
291- ) ;
292- const sort_model = Gtk . SortListModel . new ( tree_model , sorter ) ;
293- const selection_model = Gtk . SingleSelection . new ( sort_model ) ;
361+ const selection_model = Gtk . SingleSelection . new ( tree_model ) ;
294362 selection_model . connect ( "selection-changed" , ( ) => {
363+ // If selection changed to sync the sidebar, dont load_uri again
364+ if ( sync_sidebar ) {
365+ sync_sidebar = false ;
366+ return ;
367+ }
295368 const uri = selection_model . selected_item . item . uri ;
296369 webview . load_uri ( uri ) ;
297370 } ) ;
@@ -345,12 +418,13 @@ function getChildren(index, dir) {
345418 location = subsections [ symbol . type_name ] [ symbol . type ] ;
346419 }
347420 if ( location )
348- location . append (
421+ location . insert_sorted (
349422 new DocumentationPage ( {
350423 name : symbol . name ,
351424 search_name : getSearchNameForDocument ( symbol , index . meta ) ,
352425 uri : `${ dir . get_uri ( ) } /${ getLinkForDocument ( symbol ) } ` ,
353426 } ) ,
427+ sortFunc ,
354428 ) ;
355429 }
356430
@@ -359,33 +433,35 @@ function getChildren(index, dir) {
359433 const sections_model = newListStore ( ) ;
360434 for ( const section in sections ) {
361435 if ( sections [ section ] . get_n_items ( ) > 0 )
362- sections_model . append (
436+ sections_model . insert_sorted (
363437 new DocumentationPage ( {
364438 name : SECTION_TYPES [ section ] [ 0 ] ,
365439 uri : `${ index_html } ${ SECTION_TYPES [ section ] [ 1 ] } ` ,
366440 children : sections [ section ] ,
367441 } ) ,
442+ sortFunc ,
368443 ) ;
369444 }
370445 return sections_model ;
371446}
372447
373448const REQUIRED = [ "class" , "interface" , "record" , "domain" ] ;
374-
375449function createSubsections ( subsections , sections ) {
376450 for ( const type of REQUIRED ) {
377451 for ( const item of sections [ type ] ) {
378452 const model = newListStore ( ) ;
379453 const name = item . name ;
380454 for ( const subsection in subsections [ name ] ) {
381- if ( subsections [ name ] [ subsection ] . get_n_items ( ) > 0 )
382- model . append (
455+ if ( subsections [ name ] [ subsection ] . get_n_items ( ) > 0 ) {
456+ model . insert_sorted (
383457 new DocumentationPage ( {
384458 name : SUBSECTION_TYPES [ subsection ] [ 0 ] ,
385459 uri : `${ item . uri } ${ SUBSECTION_TYPES [ subsection ] [ 1 ] } ` ,
386460 children : subsections [ name ] [ subsection ] ,
387461 } ) ,
462+ sortFunc ,
388463 ) ;
464+ }
389465 }
390466 item . children = model ;
391467 }
0 commit comments