1- var last_sort_by = 'rank' ;
2-
3- $ ( '#sort-by-hn-rank' ) . click ( function ( ) {
4- $ ( 'article' ) . sort ( function ( a , b ) {
5- if ( last_sort_by === 'rank' )
6- return $ ( b ) . data ( 'rank' ) - $ ( a ) . data ( 'rank' ) ;
7- return $ ( a ) . data ( 'rank' ) - $ ( b ) . data ( 'rank' ) ;
8- } ) . insertBefore ( $ ( 'footer' ) ) ;
9- if ( last_sort_by === 'rank' ) {
10- last_sort_by = '' ;
11- } else {
12- last_sort_by = 'rank' ;
13- }
14- $ ( '.navbar-nav>li.sort-dropdown' ) . toggleClass ( "open" ) ;
15- return false ;
16- } ) ;
1+ let last_sort_by = 'rank' ;
172
18- $ ( '#sort-by-score' ) . click ( function ( ) {
19- $ ( 'article' ) . sort ( function ( a , b ) {
20- var score1 = parseInt ( $ ( a ) . find ( '.post-meta .score' ) . text ( ) || 0 ) ;
21- var score2 = parseInt ( $ ( b ) . find ( '.post-meta .score' ) . text ( ) || 0 ) ;
22- if ( score1 === score2 ) {
23- return $ ( a ) . data ( 'rank' ) - $ ( b ) . data ( 'rank' ) ;
3+ function updateUrlHash ( newParams ) {
4+ const params = new URLSearchParams ( window . location . hash . substring ( 1 ) ) ;
5+ for ( const [ key , value ] of Object . entries ( newParams ) ) {
6+ if ( value !== null && value !== undefined && value !== '' ) {
7+ params . set ( key , value ) ;
8+ } else {
9+ params . delete ( key ) ;
2410 }
25- if ( last_sort_by === 'score' )
26- return score1 - score2 ;
27- return score2 - score1 ;
28- } ) . insertBefore ( $ ( 'footer' ) ) ;
29- if ( last_sort_by === 'score' ) {
30- last_sort_by = '' ;
11+ }
12+ const newHash = params . toString ( ) ;
13+ if ( history . replaceState ) {
14+ history . replaceState ( null , null , `#${ newHash } ` ) ;
3115 } else {
32- last_sort_by = 'score' ;
16+ window . location . hash = newHash ;
3317 }
34- $ ( '.navbar-nav>li.sort-dropdown' ) . toggleClass ( "open" ) ;
35- return false ;
36- } ) ;
18+ }
3719
38- $ ( '#sort-by-comments' ) . click ( function ( ) {
39- $ ( 'article' ) . sort ( function ( a , b ) {
40- var comment1 = parseInt ( $ ( a ) . find ( '.post-meta .comment' ) . text ( ) || 0 ) ;
41- var comment2 = parseInt ( $ ( b ) . find ( '.post-meta .comment' ) . text ( ) || 0 ) ;
42- if ( comment1 === comment2 ) {
43- return $ ( a ) . data ( 'rank' ) - $ ( b ) . data ( 'rank' ) ;
44- }
45- if ( last_sort_by === 'comment' )
46- return comment1 - comment2 ;
47- return comment2 - comment1 ;
48- } ) . insertBefore ( $ ( 'footer' ) ) ;
49- if ( last_sort_by === 'comment' ) {
50- last_sort_by = '' ;
51- } else {
52- last_sort_by = 'comment' ;
20+ const comparators = {
21+ 'rank' : ( a , b ) => $ ( a ) . data ( 'rank' ) - $ ( b ) . data ( 'rank' ) ,
22+ 'score' : ( a , b ) => {
23+ const score1 = parseInt ( $ ( a ) . find ( '.post-meta .score' ) . text ( ) || 0 ) ;
24+ const score2 = parseInt ( $ ( b ) . find ( '.post-meta .score' ) . text ( ) || 0 ) ;
25+ if ( score1 === score2 ) return $ ( a ) . data ( 'rank' ) - $ ( b ) . data ( 'rank' ) ;
26+ return score1 - score2 ;
27+ } ,
28+ 'comments' : ( a , b ) => {
29+ const comment1 = parseInt ( $ ( a ) . find ( '.post-meta .comment' ) . text ( ) || 0 ) ;
30+ const comment2 = parseInt ( $ ( b ) . find ( '.post-meta .comment' ) . text ( ) || 0 ) ;
31+ if ( comment1 === comment2 ) return $ ( a ) . data ( 'rank' ) - $ ( b ) . data ( 'rank' ) ;
32+ return comment1 - comment2 ;
33+ } ,
34+ 'time' : ( a , b ) => {
35+ const t1 = new Date ( $ ( a ) . find ( '.post-meta .summit-time .last-updated' ) . data ( 'submitted' ) ) ;
36+ const t2 = new Date ( $ ( b ) . find ( '.post-meta .summit-time .last-updated' ) . data ( 'submitted' ) ) ;
37+ if ( t1 . getTime ( ) === t2 . getTime ( ) ) return $ ( a ) . data ( 'rank' ) - $ ( b ) . data ( 'rank' ) ;
38+ return t2 - t1 ; // Descending order for time
5339 }
54- $ ( '.navbar-nav>li.sort-dropdown' ) . toggleClass ( "open" ) ;
55- return false ;
56- } ) ;
40+ } ;
5741
58- $ ( '#sort-by-submit-time' ) . click ( function ( ) {
59- $ ( 'article' ) . sort ( function ( a , b ) {
60- var s_t1 = $ ( a ) . find ( '.post-meta .summit-time .last-updated' ) . data ( 'submitted' ) ;
61- var s_t2 = $ ( b ) . find ( '.post-meta .summit-time .last-updated' ) . data ( 'submitted' ) ;
62- if ( s_t1 === s_t2 ) {
63- return $ ( a ) . data ( 'rank' ) - $ ( b ) . data ( 'rank' ) ;
42+ function applyAndRenderSort ( sortBy , sortOrder ) {
43+ const comparator = comparators [ sortBy ] ;
44+ if ( ! comparator ) {
45+ console . error ( `Unknown sort type: ${ sortBy } ` ) ;
46+ return ;
47+ }
48+
49+ const articles = $ ( 'article' ) ;
50+ const items = articles . get ( ) ; // Get all articles as a plain array
51+
52+ // Store ads and their original indices. Ads are identified by the '.ad' class.
53+ const ads = [ ] ;
54+ items . forEach ( function ( item , index ) {
55+ if ( $ ( item ) . hasClass ( 'ad' ) ) {
56+ ads . push ( { index : index , element : item } ) ;
6457 }
65- var t1 = new Date ( s_t1 ) ;
66- var t2 = new Date ( s_t2 ) ;
67- //var t1 = parseInt(s_t1 || 0);
68- //var t2 = parseInt(s_t2 || 0);
69- //if(/minute/i.test(s_t1)) t1 *= 60;
70- //if(/minute/i.test(s_t2)) t2 *= 60;
71- //if(/hour/i.test(s_t1)) t1 *= 3600;
72- //if(/hour/i.test(s_t2)) t2 *= 3600;
73- //if(/day/i.test(s_t1)) t1 *= 86400;
74- //if(/day/i.test(s_t2)) t2 *= 86400;
75- if ( last_sort_by === 'submit-time' )
76- return t1 - t2 ;
77- return t2 - t1 ;
78- } ) . insertBefore ( $ ( 'footer' ) ) ;
79- if ( last_sort_by === 'submit-time' ) {
80- last_sort_by = '' ;
81- } else {
82- last_sort_by = 'submit-time' ;
58+ } ) ;
59+
60+ // Filter out ads to get only news items for sorting
61+ const newsItems = items . filter ( function ( item ) {
62+ return ! $ ( item ) . hasClass ( 'ad' ) ;
63+ } ) ;
64+
65+ // Sort the news items
66+ newsItems . sort ( function ( a , b ) {
67+ const result = comparator ( a , b ) ;
68+ return sortOrder === 'desc' ? - result : result ;
69+ } ) ;
70+
71+ // Re-insert ads into the sorted list at their original indices to maintain their position
72+ ads . forEach ( function ( ad ) {
73+ newsItems . splice ( ad . index , 0 , ad . element ) ;
74+ } ) ;
75+
76+ // Detach all original articles from the DOM
77+ articles . detach ( ) ;
78+
79+ // And then insert the newly ordered list of items (news + ads) back
80+ $ ( newsItems ) . insertBefore ( $ ( 'footer' ) ) ;
81+
82+ updateUrlHash ( { sort : sortBy , order : sortOrder } ) ;
83+ }
84+
85+ function applyAndRenderFilter ( topN ) {
86+ if ( ! topN || topN <= 0 ) {
87+ $ ( 'article' ) . show ( ) ;
88+ return ;
8389 }
84- $ ( '.navbar-nav>li.sort-dropdown' ) . toggleClass ( "open" ) ;
85- return false ;
86- } ) ;
87- // Filter by
88- $ ( '.navbar-nav>li.filter-dropdown .dropdown-menu a' ) . click ( function ( e ) {
89- let topN = parseInt ( $ ( this ) . data ( 'top' ) ) ;
90- let points = $ . map ( $ ( 'article' ) , function ( e ) {
90+
91+ let points = $ . map ( $ ( 'article' ) , function ( e ) {
9192 return parseInt ( $ ( e ) . find ( '.post-meta .score' ) . text ( ) || 0 ) ;
92- } ) . sort ( function ( a , b ) {
93+ } ) . sort ( function ( a , b ) {
9394 return b - a ;
9495 } ) ;
96+
9597 let threshold = 0 ;
9698 if ( topN < points . length ) {
97- threshold = points [ topN - 1 ] ;
99+ threshold = points [ topN - 1 ] ;
98100 }
99- $ ( 'article' ) . each ( function ( ) {
101+
102+ $ ( 'article' ) . each ( function ( ) {
100103 let scoreDom = $ ( this ) . find ( '.post-meta .score' ) ;
101104 if ( ! scoreDom . length ) {
102- return ; // ads
105+ return ; // ads
103106 }
104107 let point = parseInt ( scoreDom . text ( ) || 0 ) ;
105108 if ( point >= threshold ) {
@@ -108,9 +111,69 @@ $('.navbar-nav>li.filter-dropdown .dropdown-menu a').click(function (e){
108111 $ ( this ) . hide ( ) ;
109112 }
110113 } ) ;
111- $ ( '.navbar-nav>li.filter-dropdown' ) . toggleClass ( "open" ) ;
112- return false ;
114+ }
115+
116+ function setupSortHandlers ( ) {
117+ const sortConfig = {
118+ '#sort-by-hn-rank' : { key : 'rank' , internal : 'rank' } ,
119+ '#sort-by-score' : { key : 'score' , internal : 'score' } ,
120+ '#sort-by-comments' : { key : 'comments' , internal : 'comment' } ,
121+ '#sort-by-submit-time' : { key : 'time' , internal : 'submit-time' }
122+ } ;
123+
124+ for ( const [ buttonId , config ] of Object . entries ( sortConfig ) ) {
125+ $ ( buttonId ) . click ( function ( ) {
126+ const sortOrder = ( last_sort_by === config . internal ) ? 'asc' : 'desc' ;
127+ applyAndRenderSort ( config . key , sortOrder ) ;
128+ last_sort_by = ( sortOrder === 'desc' ) ? config . internal : '' ;
129+ $ ( '.navbar-nav>li.sort-dropdown' ) . removeClass ( "open" ) ;
130+ return false ; // Prevent default link behavior so that we can update the URL hash
131+ } ) ;
132+ }
133+ }
134+
135+ function setupFilterHandlers ( ) {
136+ $ ( '.navbar-nav>li.filter-dropdown .dropdown-menu a' ) . click ( function ( ) {
137+ let topN = parseInt ( $ ( this ) . data ( 'top' ) ) ;
138+ applyAndRenderFilter ( topN ) ;
139+ if ( topN === - 1 ) {
140+ topN = '' ; // Clear filter
141+ }
142+ updateUrlHash ( { filter : topN } ) ;
143+ $ ( '.navbar-nav>li.filter-dropdown' ) . removeClass ( "open" ) ;
144+ return false ;
145+ } ) ;
146+ }
147+
148+ // Initial setup on page load
149+ $ ( function ( ) {
150+ setupSortHandlers ( ) ;
151+ setupFilterHandlers ( ) ;
152+
153+ const urlParams = new URLSearchParams ( window . location . hash . substring ( 1 ) ) ;
154+
155+ // Filter first
156+ const filterBy = urlParams . get ( 'filter' ) ;
157+ if ( filterBy ) {
158+ applyAndRenderFilter ( parseInt ( filterBy ) ) ;
159+ }
160+
161+ // Apply sorting from hash
162+ const sortBy = urlParams . get ( 'sort' ) ;
163+ const sortOrder = urlParams . get ( 'order' ) || 'desc' ;
164+ if ( sortBy ) {
165+ if ( comparators [ sortBy ] ) {
166+ applyAndRenderSort ( sortBy , sortOrder ) ;
167+ const internalKeyMap = {
168+ 'rank' : 'rank' , 'score' : 'score' , 'comments' : 'comment' , 'time' : 'submit-time'
169+ } ;
170+ last_sort_by = ( sortOrder === 'desc' ) ? internalKeyMap [ sortBy ] : '' ;
171+ } else {
172+ console . error ( `Unknown sort type: ${ sortBy } ` ) ;
173+ }
174+ }
113175} ) ;
176+
114177// We don't need to wait for the document.ready event, that costs a lot of time.
115178$ . scrollUp ( {
116179 scrollTrigger : '<i class="fa fa-chevron-circle-up fa-3x" id="scrollUp"></i>' ,
0 commit comments