Skip to content

Commit 8493f17

Browse files
authored
Merge pull request #1755 from makermelissa-piclaw/add-sort-options-contributing
Add sort options to Contributing Open Issues page
2 parents 9b45463 + b3c9de4 commit 8493f17

File tree

3 files changed

+206
-30
lines changed

3 files changed

+206
-30
lines changed

assets/javascript/contributing.js

Lines changed: 167 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
document.addEventListener('DOMContentLoaded', function() {
22
// only load on open issues page for now
3-
var issueSelect = document.querySelector(".open-issues select");
3+
var issueSelect = document.querySelector(".open-issues #label-filter");
44
if (!issueSelect) {
55
return;
66
}
77

8-
issueSelect.onchange = issueSelectHandler;
8+
issueSelect.onchange = function(event) {
9+
issueSelectHandler(event);
10+
applySorting();
11+
};
12+
13+
var sortSelect = document.querySelector(".open-issues #sort-order");
14+
if (sortSelect) {
15+
sortSelect.onchange = function() {
16+
applySorting();
17+
};
18+
}
919

1020
// load issues label when using back button
1121
window.addEventListener('popstate', loadIssues.bind(null, true));
@@ -17,27 +27,35 @@ document.addEventListener('DOMContentLoaded', function() {
1727
function loadIssues(isPopState) {
1828
var params = new URLSearchParams(window.location.search);
1929
var label = params.get('label');
30+
var sort = params.get('sort');
2031

21-
if (!label) {
22-
return;
32+
if (sort) {
33+
var sortSelect = document.querySelector('.open-issues #sort-order');
34+
if (sortSelect) {
35+
sortSelect.value = sort;
36+
}
37+
}
38+
39+
if (label) {
40+
issueSelectHandler(label, isPopState);
41+
var issuesList = document.querySelector('.open-issues #label-filter');
42+
issuesList.value = label;
2343
}
2444

25-
issueSelectHandler(label, isPopState);
26-
var issuesList = document.querySelector('.open-issues select');
27-
issuesList.value = label;
45+
applySorting();
2846
}
2947

3048
function issueSelectHandler(event, isPopState) {
3149
if (event.target) {
32-
var selectedOption = this.options[this.selectedIndex].value;
50+
var selectedOption = event.target.options[event.target.selectedIndex].value;
3351
} else {
3452
// page loads will set the event as just the selected label from params
3553
var selectedOption = event;
3654
}
3755

3856
// don't set params on the back button
3957
if (!isPopState) {
40-
setIssueParams(selectedOption);
58+
setParams();
4159
}
4260

4361
// hide all elements first
@@ -47,17 +65,150 @@ function issueSelectHandler(event, isPopState) {
4765
});
4866

4967
// show the selected options
50-
var selectedOption = selectedOption === 'all' ? 'li' : `.${selectedOption}`;
51-
var items = document.querySelectorAll(`.issues-list ${selectedOption}`);
68+
var selector = selectedOption === 'all' ? 'li' : '.' + selectedOption;
69+
var items = document.querySelectorAll('.issues-list ' + selector);
5270
items.forEach(function(item) {
53-
item.style.display = 'block'
71+
item.style.display = 'block';
5472
item.parentElement.closest('li').style.display = 'block';
5573
});
5674
}
5775

58-
function setIssueParams(label) {
76+
function getIssueDays(element) {
77+
return parseInt(element.dataset.daysOpen, 10) || 0;
78+
}
79+
80+
function applySorting() {
81+
var sortSelect = document.querySelector('.open-issues #sort-order');
82+
if (!sortSelect) return;
83+
84+
var sortOrder = sortSelect.value;
85+
if (sortOrder === 'default') {
86+
// Restore original order by reloading — but simpler to just not sort
87+
// We store original order on first run
88+
restoreOriginalOrder();
89+
setParams();
90+
return;
91+
}
92+
93+
// Sort issues within each library's issues-list
94+
var issuesLists = document.querySelectorAll('.issues-list');
95+
issuesLists.forEach(function(list) {
96+
var items = Array.from(list.querySelectorAll(':scope > li'));
97+
98+
// Store original order if not already stored
99+
if (!list.dataset.originalOrder) {
100+
list.dataset.originalOrder = 'stored';
101+
items.forEach(function(item, index) {
102+
item.dataset.originalIndex = index;
103+
});
104+
}
105+
106+
items.sort(function(a, b) {
107+
var daysA = getIssueDays(a);
108+
var daysB = getIssueDays(b);
109+
if (sortOrder === 'newest') {
110+
return daysA - daysB; // fewer days = newer = first
111+
} else {
112+
return daysB - daysA; // more days = older = first
113+
}
114+
});
115+
116+
// Re-append in sorted order
117+
items.forEach(function(item) {
118+
list.appendChild(item);
119+
});
120+
});
121+
122+
// Sort the library groups by their best matching issue
123+
var topList = document.getElementById('libraries-list');
124+
if (topList) {
125+
var libraryItems = Array.from(topList.querySelectorAll(':scope > li'));
126+
127+
if (!topList.dataset.originalOrder) {
128+
topList.dataset.originalOrder = 'stored';
129+
libraryItems.forEach(function(item, index) {
130+
item.dataset.originalIndex = index;
131+
});
132+
}
133+
134+
libraryItems.sort(function(a, b) {
135+
var bestA = getBestDays(a.querySelectorAll('.issues-list > li'), sortOrder);
136+
var bestB = getBestDays(b.querySelectorAll('.issues-list > li'), sortOrder);
137+
if (sortOrder === 'newest') {
138+
return bestA - bestB;
139+
} else {
140+
return bestB - bestA;
141+
}
142+
});
143+
144+
libraryItems.forEach(function(item) {
145+
topList.appendChild(item);
146+
});
147+
}
148+
149+
setParams();
150+
}
151+
152+
function getBestDays(issues, sortOrder) {
153+
var result = sortOrder === 'newest' ? Infinity : 0;
154+
issues.forEach(function(issue) {
155+
var days = getIssueDays(issue);
156+
if (sortOrder === 'newest') {
157+
result = Math.min(result, days);
158+
} else {
159+
result = Math.max(result, days);
160+
}
161+
});
162+
return result === Infinity ? 0 : result;
163+
}
164+
165+
function restoreOriginalOrder() {
166+
// Restore library-level order
167+
var topList = document.getElementById('libraries-list');
168+
if (topList && topList.dataset.originalOrder) {
169+
var libraryItems = Array.from(topList.querySelectorAll(':scope > li'));
170+
libraryItems.sort(function(a, b) {
171+
return (parseInt(a.dataset.originalIndex) || 0) - (parseInt(b.dataset.originalIndex) || 0);
172+
});
173+
libraryItems.forEach(function(item) {
174+
topList.appendChild(item);
175+
});
176+
}
177+
178+
// Restore issue-level order within each list
179+
var issuesLists = document.querySelectorAll('.issues-list');
180+
issuesLists.forEach(function(list) {
181+
var items = Array.from(list.querySelectorAll(':scope > li'));
182+
items.sort(function(a, b) {
183+
return (parseInt(a.dataset.originalIndex) || 0) - (parseInt(b.dataset.originalIndex) || 0);
184+
});
185+
items.forEach(function(item) {
186+
list.appendChild(item);
187+
});
188+
});
189+
}
190+
191+
function setParams() {
59192
var params = new URLSearchParams(window.location.search);
60-
params.set("label", label);
61-
var newUrl = `${window.location.protocol}//${window.location.host}${window.location.pathname}?${params.toString()}`;
62-
window.history.pushState({path:newUrl}, '', newUrl);
193+
194+
var labelSelect = document.querySelector('.open-issues #label-filter');
195+
if (labelSelect && labelSelect.value && labelSelect.value !== 'all') {
196+
params.set("label", labelSelect.value);
197+
} else {
198+
params.delete("label");
199+
}
200+
201+
var sortSelect = document.querySelector('.open-issues #sort-order');
202+
if (sortSelect && sortSelect.value && sortSelect.value !== 'default') {
203+
params.set("sort", sortSelect.value);
204+
} else {
205+
params.delete("sort");
206+
}
207+
208+
var query = params.toString();
209+
var newUrl = window.location.protocol + '//' + window.location.host + window.location.pathname;
210+
if (query) {
211+
newUrl += '?' + query;
212+
}
213+
window.history.pushState({path: newUrl}, '', newUrl);
63214
}

assets/sass/pages/_contributing.scss

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,21 @@
2626
}
2727
}
2828

29+
.issue-controls {
30+
display: flex;
31+
gap: 2em;
32+
flex-wrap: wrap;
33+
align-items: center;
34+
35+
p {
36+
margin: 0.5em 0;
37+
}
38+
39+
select {
40+
margin-left: 0.5em;
41+
}
42+
}
43+
2944
ul.issues-list {
3045
li {
3146
.issue-label {

contributing/open_issues.html

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,20 +17,30 @@
1717
{% endfor %}
1818
{% endfor %}
1919
{% endfor %}
20-
<p>
21-
Filter by issue labels
22-
<select>
23-
{% for label in labels %}
24-
{% assign lowerlabel = label | downcase %}
25-
{% assign ddlabels = ddlabels | push: lowerlabel | uniq %}
26-
{% endfor %}
27-
{% for label in ddlabels %}
28-
<option value='{{ label | replace: ' ', '-' }}'>{{ label | capitalize }}</option>
29-
{% endfor %}
30-
<select>
31-
</p>
20+
<div class="issue-controls">
21+
<p>
22+
Filter by issue labels
23+
<select id="label-filter">
24+
{% for label in labels %}
25+
{% assign lowerlabel = label | downcase %}
26+
{% assign ddlabels = ddlabels | push: lowerlabel | uniq %}
27+
{% endfor %}
28+
{% for label in ddlabels %}
29+
<option value='{{ label | replace: ' ', '-' }}'>{{ label | capitalize }}</option>
30+
{% endfor %}
31+
</select>
32+
</p>
33+
<p>
34+
Sort by
35+
<select id="sort-order">
36+
<option value="default">Default</option>
37+
<option value="newest">Newest first</option>
38+
<option value="oldest">Oldest first</option>
39+
</select>
40+
</p>
41+
</div>
3242
<h3>Open Issues</h3>
33-
<ul>
43+
<ul id="libraries-list">
3444
{% for library in site.data.libraries.open_issues %}
3545
<li>
3646
{{library[0]}}
@@ -41,7 +51,7 @@ <h3>Open Issues</h3>
4151
{{ label | downcase | replace: ' ', '-' }}
4252
{% endfor %}
4353
{% endcapture %}
44-
<li class="{{ classes | strip }}"><a href="{{ issue.url }}">{{ issue.title }}</a>
54+
<li class="{{ classes | strip }}" data-days-open="{{ issue.days_open }}"><a href="{{ issue.url }}">{{ issue.title }}</a>
4555
{% for label in issue.labels %}
4656
{% if label != 'None' %}
4757
<span class="issue-label tag-{{ label | downcase | replace: ' ', '-' }}">{{label}}</span>

0 commit comments

Comments
 (0)