Skip to content

Commit 1afb3cd

Browse files
authored
Add search/browse form for users without JavaScript (#1052)
1 parent 4a46d16 commit 1afb3cd

File tree

10 files changed

+387
-212
lines changed

10 files changed

+387
-212
lines changed

datafiles/static/browse.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,18 @@ addEventListener('popstate', async (evt) => {
3636
});
3737

3838
const get = () => new Promise((resolve,reject) => {
39-
const formData = new FormData();
4039
const obj =
4140
{ page: state.page
42-
, sort: {column: state.column, direction: state.direction}
41+
, sortColumn: state.column
42+
, sortDirection: state.direction
4343
, searchQuery: state.searchQuery
4444
};
45-
formData.append('browseOptions', JSON.stringify(obj));
46-
fetch('/packages/search', {method:'POST', body: formData}).then(async (response) => {
45+
const fetchOptions =
46+
{ method: 'POST'
47+
, headers: {'content-type': 'application/json'}
48+
, body: JSON.stringify(obj)
49+
};
50+
fetch('/packages/search', fetchOptions).then(async (response) => {
4751
if (!response.ok) {
4852
const el = d.querySelector("#fatalError");
4953
el.style.display = "block";

datafiles/templates/Html/browse.html.st

Lines changed: 109 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -159,104 +159,120 @@
159159
font-family: monospace;
160160
}
161161
</style>
162+
<noscript>
163+
<style>
164+
.javaScriptOnly {
165+
display: none;
166+
}
167+
</style>
168+
</noscript>
162169
</head>
163170
<body>
164171
$hackagePageHeader()$
165172
<div id=content>
166173
<h1>$heading$</h1>
167-
$content$
168-
<div id=fatalError></div>
169-
<form onsubmit="javascript: submitSearch(event)">
170-
<input type=text id=searchQuery />
171-
<input type=submit value="Search" />
172-
</form>
173-
<h3 id=toggleAdvanced>
174-
<a aria-expanded=false aria-controls=advancedForm href="javascript: toggleAdvanced();">
175-
<span id=chevron>&#x25B8;</span>
176-
Advanced options
177-
</a>
178-
</h3>
179-
<div id=advancedForm>
180-
<form class=filterSuggestion onsubmit="javascript:appendDeprecated(event);"><input type=submit value=Append> <div>Also show deprecated packages</div></form>
181-
<form class=filterSuggestion onsubmit="javascript:appendAgeOfLastUL(event);"><input type=submit value=Append><div><span>Last uploaded version younger than</span><input placeholder="e.g. 2y for 2 years" id=advAgeLastUL oninput="validateAgeOfLastUL();"></div></form>
182-
<form class=filterSuggestion onsubmit="javascript:appendTag(event);"><input type=submit value=Append> <div><span>Only show packages with tag</span><input id=advTag placeholder="e.g. bsd3" oninput="validateTag();"></div></form>
183-
<form class=filterSuggestion onsubmit="javascript:appendRating(event);"><input type=submit value=Append> <div><span>Rating greater than, or equal to</span><span id=sliderAndOutput><input id=advRatingSlider type=range min=0 max=3 step=.1 oninput="this.nextElementSibling.value = this.value" value=2><output>2</output></span></div></form>
184-
<h5>Usage</h5>
185-
<p>Apart from just writing words to search everywhere in package metadata,
186-
you can also use filters in the search query string input field above.
187-
Filters are surrounded by parentheses.
188-
All filters have to pass for every package shown in the result,
189-
that is, it is a
190-
<a href="https://en.wikipedia.org/wiki/Logical_conjunction" target=_blank>
191-
logical conjunction</a>.
192-
</p>
193-
<dl>
194-
<dt>(downloads > 1000)</dt>
195-
<dd>Only show packages with more than 1000 downloads within the last 30 days. The download count is inexact because Hackage uses a <a href="https://en.wikipedia.org/wiki/Content_delivery_network" target=_blank>content delivery network</a>.</dd>
196-
<dt>(lastUpload &lt; 2021-10-29)</dt>
197-
<dd>Only show packages for which the last upload was before (i.e. excluding) the given UTC date in <a target=_blank href="https://www.w3.org/TR/NOTE-datetime">the 'complete date' format as specified using ISO 8601</a>.</dd>
198-
<dt>(lastUpload = 2021-10-29)</dt>
199-
<dd>Only show packages for which the last upload was within the 24 hours of the given UTC date.</dd>
200-
<dt>(maintainer:SimonMarlow)</dt>
201-
<dd>Only show packages for which the maintainers list includes the user name <a target=_blank href="/user/SimonMarlow">SimonMarlow</a>.</dd>
202-
<dt>(tag:bsd3)</dt>
203-
<dd>Only show packages with the <code><a target=_blank href="/packages/tag/bsd3">bsd3</a></code> tag.</dd>
204-
<dt>(not tag:network)</dt>
205-
<dd>Do not show packages with the <code><a target=_blank href="/packages/tag/network">network</a></code> tag. The <code>not</code> operator can also be used with other filters.</dd>
206-
<dt>(ageOfLastUpload > 5d)</dt>
207-
<dd>Only show packages uploaded more than five days ago.</dd>
208-
<dt>(ageOfLastUpload > 4w)</dt>
209-
<dd>Only show packages uploaded more than four weeks ago. A week has seven days.</dd>
210-
<dt>(ageOfLastUpload &lt; 1m)</dt>
211-
<dd>Only show packages last uploaded less than one month ago. A month is considered to have 30.437 days.</dd>
212-
<dt>(ageOfLastUpload &lt; 2.5y)</dt>
213-
<dd>Only show packages last uploaded less than 2.5 years ago. A year is considered to be 365.25 days.</dd>
214-
<dt>(rating > 2.5)</dt>
215-
<dd>Only show packages with a rating of more than 2.5. The dot is the only accepted decimal separator.</dd>
216-
<dt>(rating /= 0)</dt>
217-
<dd>Only show packages with a rating unequal to zero.</dd>
218-
<dt>(deprecated:any)</dt>
219-
<dd>Do not filter out deprecated packages. This must be explicitly added if desired.</dd>
220-
<dt>(deprecated:true)</dt>
221-
<dd>Only show deprecated packages.</dd>
222-
<dt>(deprecated:false)</dt>
223-
<dd>Only show packages that are not deprecated. If no other deprecation filter is given, this filter is automatically added.</dd>
224-
<dt>(distro:Debian)</dt>
225-
<dd>Only show packages that are available in the Debian distribution. See the <a href="/distros">full list of available distributions</a>.</dd>
226-
</dl>
227-
</div>
228-
<table id=browseTable class=fancy>
229-
<thead>
230-
<tr>
231-
<th id=arrow-name><a href="javascript: sort('name')">Name</a></th>
232-
<th id=arrow-downloads title="Over the last 30 days"><a href="javascript: sort('downloads')">DLs</a></th>
233-
<th id=arrow-rating title="Ranges from 0 to 3"><a href="javascript: sort('rating')">Rating</a></th>
234-
<th id=arrow-description><a href="javascript: sort('description')">Description</a></th>
235-
<th id=arrow-tags><a href="javascript: sort('tags')">Tags</a></th>
236-
<th id=arrow-lastUpload><a href="javascript: sort('lastUpload')">Last U/L</a></th>
237-
<th id=arrow-maintainers><a href="javascript: sort('maintainers')">Maintainers</a></th>
238-
</tr>
239-
</thead>
240-
<tbody id="listing"></tbody>
241-
</table>
242-
<script type=module>
243-
import { sort, submitSearch, toggleAdvanced, appendDeprecated, appendAgeOfLastUL,
244-
appendTag, appendRating, validateAgeOfLastUL, validateTag }
245-
from "/static/browse.js";
246-
window.sort = sort;
247-
window.submitSearch = submitSearch;
248-
window.toggleAdvanced = toggleAdvanced;
249-
window.appendDeprecated = appendDeprecated;
250-
window.appendAgeOfLastUL = appendAgeOfLastUL;
251-
window.appendTag = appendTag;
252-
window.appendRating = appendRating;
253-
window.validateAgeOfLastUL = validateAgeOfLastUL;
254-
window.validateTag = validateTag;
255-
</script>
256-
<div id=paginatorContainer>
257-
</div>
258-
<div id=browseFooter>
259-
Alternatively, if you are looking for a particular function then try <a href="#" id=hoogleLink target=_blank>Hoogle</a>.
174+
<noscript>
175+
$formFragment$
176+
<!-- Elinks does not understand CSS, so it will show the full page.
177+
We add this warning such that users will not use the wrong form.
178+
The class attribute hides the notice for Firefox/Chrome users.
179+
-->
180+
<p class=javaScriptOnly>Please disregard the following form, it only works with JavaScript.</p>
181+
</noscript>
182+
<div class=javaScriptOnly>
183+
<div id=fatalError></div>
184+
<form onsubmit="javascript: submitSearch(event)">
185+
<input type=text id=searchQuery />
186+
<input type=submit value="Search" />
187+
</form>
188+
<h3 id=toggleAdvanced>
189+
<a aria-expanded=false aria-controls=advancedForm href="javascript: toggleAdvanced();">
190+
<span id=chevron>&#x25B8;</span>
191+
Advanced options
192+
</a>
193+
</h3>
194+
<div id=advancedForm>
195+
<form class=filterSuggestion onsubmit="javascript:appendDeprecated(event);"><input type=submit value=Append> <div>Also show deprecated packages</div></form>
196+
<form class=filterSuggestion onsubmit="javascript:appendAgeOfLastUL(event);"><input type=submit value=Append><div><span>Last uploaded version younger than</span><input placeholder="e.g. 2y for 2 years" id=advAgeLastUL oninput="validateAgeOfLastUL();"></div></form>
197+
<form class=filterSuggestion onsubmit="javascript:appendTag(event);"><input type=submit value=Append> <div><span>Only show packages with tag</span><input id=advTag placeholder="e.g. bsd3" oninput="validateTag();"></div></form>
198+
<form class=filterSuggestion onsubmit="javascript:appendRating(event);"><input type=submit value=Append> <div><span>Rating greater than, or equal to</span><span id=sliderAndOutput><input id=advRatingSlider type=range min=0 max=3 step=.1 oninput="this.nextElementSibling.value = this.value" value=2><output>2</output></span></div></form>
199+
<h5>Usage</h5>
200+
<p>Apart from just writing words to search everywhere in package metadata,
201+
you can also use filters in the search query string input field above.
202+
Filters are surrounded by parentheses.
203+
All filters have to pass for every package shown in the result,
204+
that is, it is a
205+
<a href="https://en.wikipedia.org/wiki/Logical_conjunction" target=_blank>
206+
logical conjunction</a>.
207+
</p>
208+
<dl>
209+
<dt>(downloads > 1000)</dt>
210+
<dd>Only show packages with more than 1000 downloads within the last 30 days. The download count is inexact because Hackage uses a <a href="https://en.wikipedia.org/wiki/Content_delivery_network" target=_blank>content delivery network</a>.</dd>
211+
<dt>(lastUpload &lt; 2021-10-29)</dt>
212+
<dd>Only show packages for which the last upload was before (i.e. excluding) the given UTC date in <a target=_blank href="https://www.w3.org/TR/NOTE-datetime">the 'complete date' format as specified using ISO 8601</a>.</dd>
213+
<dt>(lastUpload = 2021-10-29)</dt>
214+
<dd>Only show packages for which the last upload was within the 24 hours of the given UTC date.</dd>
215+
<dt>(maintainer:SimonMarlow)</dt>
216+
<dd>Only show packages for which the maintainers list includes the user name <a target=_blank href="/user/SimonMarlow">SimonMarlow</a>.</dd>
217+
<dt>(tag:bsd3)</dt>
218+
<dd>Only show packages with the <code><a target=_blank href="/packages/tag/bsd3">bsd3</a></code> tag.</dd>
219+
<dt>(not tag:network)</dt>
220+
<dd>Do not show packages with the <code><a target=_blank href="/packages/tag/network">network</a></code> tag. The <code>not</code> operator can also be used with other filters.</dd>
221+
<dt>(ageOfLastUpload > 5d)</dt>
222+
<dd>Only show packages uploaded more than five days ago.</dd>
223+
<dt>(ageOfLastUpload > 4w)</dt>
224+
<dd>Only show packages uploaded more than four weeks ago. A week has seven days.</dd>
225+
<dt>(ageOfLastUpload &lt; 1m)</dt>
226+
<dd>Only show packages last uploaded less than one month ago. A month is considered to have 30.437 days.</dd>
227+
<dt>(ageOfLastUpload &lt; 2.5y)</dt>
228+
<dd>Only show packages last uploaded less than 2.5 years ago. A year is considered to be 365.25 days.</dd>
229+
<dt>(rating > 2.5)</dt>
230+
<dd>Only show packages with a rating of more than 2.5. The dot is the only accepted decimal separator.</dd>
231+
<dt>(rating /= 0)</dt>
232+
<dd>Only show packages with a rating unequal to zero.</dd>
233+
<dt>(deprecated:any)</dt>
234+
<dd>Do not filter out deprecated packages. This must be explicitly added if desired.</dd>
235+
<dt>(deprecated:true)</dt>
236+
<dd>Only show deprecated packages.</dd>
237+
<dt>(deprecated:false)</dt>
238+
<dd>Only show packages that are not deprecated. If no other deprecation filter is given, this filter is automatically added.</dd>
239+
<dt>(distro:Debian)</dt>
240+
<dd>Only show packages that are available in the Debian distribution. See the <a href="/distros">full list of available distributions</a>.</dd>
241+
</dl>
242+
</div>
243+
<table id=browseTable class=fancy>
244+
<thead>
245+
<tr>
246+
<th id=arrow-name><a href="javascript: sort('name')">Name</a></th>
247+
<th id=arrow-downloads title="Over the last 30 days"><a href="javascript: sort('downloads')">DLs</a></th>
248+
<th id=arrow-rating title="Ranges from 0 to 3"><a href="javascript: sort('rating')">Rating</a></th>
249+
<th id=arrow-description><a href="javascript: sort('description')">Description</a></th>
250+
<th id=arrow-tags><a href="javascript: sort('tags')">Tags</a></th>
251+
<th id=arrow-lastUpload><a href="javascript: sort('lastUpload')">Last U/L</a></th>
252+
<th id=arrow-maintainers><a href="javascript: sort('maintainers')">Maintainers</a></th>
253+
</tr>
254+
</thead>
255+
<tbody id="listing"></tbody>
256+
</table>
257+
<script type=module>
258+
import { sort, submitSearch, toggleAdvanced, appendDeprecated, appendAgeOfLastUL,
259+
appendTag, appendRating, validateAgeOfLastUL, validateTag }
260+
from "/static/browse.js";
261+
window.sort = sort;
262+
window.submitSearch = submitSearch;
263+
window.toggleAdvanced = toggleAdvanced;
264+
window.appendDeprecated = appendDeprecated;
265+
window.appendAgeOfLastUL = appendAgeOfLastUL;
266+
window.appendTag = appendTag;
267+
window.appendRating = appendRating;
268+
window.validateAgeOfLastUL = validateAgeOfLastUL;
269+
window.validateTag = validateTag;
270+
</script>
271+
<div id=paginatorContainer>
272+
</div>
273+
<div id=browseFooter>
274+
Alternatively, if you are looking for a particular function then try <a href="#" id=hoogleLink target=_blank>Hoogle</a>.
275+
</div>
260276
</div>
261277
</div>
262278
</body>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Showing $minIdx$ to $maxIdx$ of $total$ entries.
2+
$if(hasNext)$
3+
<form method='POST' enctype='multipart/form-data'>
4+
<input type=hidden name='_method' value='POST'>
5+
<input type=hidden name='_transform' value='form2json'>
6+
<input type=hidden name='page=%n' value='$page$'>
7+
<input type=hidden name='searchQuery=%s' value='$searchQuery$'>
8+
<input type=hidden name='sortDirection=%s' value='$sortDirection$'>
9+
<input type=hidden name='sortColumn=%s' value='$sortColumn$'>
10+
<input type=submit value='Next page (page $page$)'>
11+
</form>
12+
$else$
13+
<p>There are no more pages of results for this query.
14+
$endif$
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<p>To view all packages, submit the form with an empty search query.</p>
2+
<form method="POST" action="/packages/noscript-search" enctype="multipart/form-data">
3+
<input type=hidden name="_method" value="POST">
4+
<input type=hidden name="_transform" value="form2json">
5+
<div>
6+
<label>
7+
Sort direction:
8+
<select name="sortDirection=%s">
9+
<option $if(ascending)$ selected $endif$ value=ascending>ascending</option>
10+
<option $if(descending)$ selected $endif$ value=descending>descending</option>
11+
</select>
12+
</label>
13+
</div>
14+
<div>
15+
<label>
16+
Sort column:
17+
<select name="sortColumn=%s">
18+
<option $if(default)$ selected $endif$ value=default>default</option>
19+
<option $if(name)$ selected $endif$ value=name>name</option>
20+
<option $if(downloads)$ selected $endif$ value=downloads>number of downloads</option>
21+
<option $if(rating)$ selected $endif$ value=rating>rating</option>
22+
<option $if(description)$ selected $endif$ value=description>description</option>
23+
<option $if(tags)$ selected $endif$ value=tags>tags</option>
24+
<option $if(lastUpload)$ selected $endif$ value=lastUpload>date of last upload</option>
25+
<option $if(maintainers)$ selected $endif$ value=maintainers>maintainers</option>
26+
</select>
27+
</label>
28+
</div>
29+
<div>
30+
<label>
31+
Search query (leave empty to show all non-deprecated packages):
32+
<input name="searchQuery=%s" value="$searchQuery$">
33+
</label>
34+
</div>
35+
<div>
36+
<label>Page number (zero-indexed):
37+
<input type=number name="page=%n" min=0 value="$pageNumber$">
38+
</label>
39+
</div>
40+
<input type=submit>
41+
</form>

datafiles/templates/Html/table-interface.html.st

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@
33

44
<head>
55
$hackageCssTheme()$
6+
$if(!noDatatable)$
67
<script src="/static/jquery.min.js"></script>
78
<link rel="stylesheet" type="text/css" href="https://cdn.datatables.net/v/dt/dt-1.11.4/sp-1.4.0/datatables.min.css"/>
89

910
<script type="text/javascript" src="https://cdn.datatables.net/v/dt/dt-1.11.4/sp-1.4.0/datatables.min.js"></script>
11+
$endif$
1012
<title>All packages by name | Hackage</title>
1113
</head>
1214

@@ -24,7 +26,7 @@
2426
<th><div style="width:50px"><span title="(0-3)">Rating</span></div></th>
2527
<th><div style="min-width:160px">Description</div></th>
2628
<th><div style="width:140px">Tags</div></th>
27-
<th><div style="width:80px">Last U/L</th>
29+
<th><div style="width:80px">Last U/L</div></th>
2830
<th><div style="width:100px">Maintainer</div></th>
2931
</tr>
3032
</thead>
@@ -34,6 +36,7 @@
3436
</table>
3537
$footer$
3638
</div>
39+
$if(!noDatatable)$
3740
<script>
3841
function filterGlobal() {
3942
\$('#table').DataTable().search(
@@ -76,6 +79,7 @@
7679
});
7780

7881
</script>
82+
$endif$
7983
</body>
8084

8185
</html>

src/Distribution/Server/Features.hs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,8 @@ initHackageFeatures env@ServerEnv{serverVerbosity = verbosity} = do
340340
listFeature
341341
searchFeature
342342
distroFeature
343-
343+
candidatesFeature
344+
344345
packageInfoJSONFeature <- mkPackageJSONFeature
345346
coreFeature
346347
versionsFeature

0 commit comments

Comments
 (0)