Skip to content

Commit e3c7022

Browse files
committed
Support reading/writing the URL hash string
1 parent 1b081a7 commit e3c7022

File tree

2 files changed

+152
-89
lines changed

2 files changed

+152
-89
lines changed

static/js/hn.js

Lines changed: 150 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,108 @@
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>',

templates/base.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@
106106
data-top="{{ news_list|length / 2 }}">Top 50%</a>
107107
</li>
108108
<li role="presentation"><a role="menuitem" tabindex="-1" href="#"
109-
data-top="1000">All</a></li>
109+
data-top="-1">All</a></li>
110110
</ul>
111111
</li>
112112
<li class="translate-dropdown" title="{{ 'Translate'|translate(lang) }}">
@@ -227,7 +227,7 @@ <h3 id="{{ news.slug() }}">
227227
</div>
228228
</article>
229229
{% if (not config.disable_ads) and (loop.index0 % 26 == 1 or loop.index0 == (news_list|length - 3)) %}
230-
<article class="post-item" data-rank="{{ loop.index0 }}">
230+
<article class="post-item ad" data-rank="{{ loop.index0 }}">
231231
<ins class="adsbygoogle"
232232
style="display:block"
233233
data-ad-format="fluid"

0 commit comments

Comments
 (0)