diff options
Diffstat (limited to 'frontend')
-rw-r--r-- | frontend/static/search.js | 66 | ||||
-rw-r--r-- | frontend/static/style.css | 23 | ||||
-rw-r--r-- | frontend/templates/blocks/options.gotmpl | 27 | ||||
-rw-r--r-- | frontend/templates/blocks/search.gotmpl | 2 |
4 files changed, 91 insertions, 27 deletions
diff --git a/frontend/static/search.js b/frontend/static/search.js index 60fa30d..2282558 100644 --- a/frontend/static/search.js +++ b/frontend/static/search.js @@ -1,12 +1,14 @@ const search = document.getElementById("search"); let results = document.getElementById("results"); +let pagination = document.getElementById("pagination"); const range = new Range(); range.setStartAfter(search); range.setEndAfter(search.parentNode.lastChild); let state = history.state || { - url: new URL(location).toJSON(), + url: new URL(location).toString(), + fragment: range.cloneContents().innerHTML || "", opened: [], }; @@ -35,44 +37,69 @@ function addToggleEventListeners(results) { details.addEventListener("toggle", detailsToggled, { passive: true }), ); } -search.addEventListener("submit", function (ev) { - const url = new URL(this.action); - url.search = new URLSearchParams(new FormData(this)).toString(); + +function paginationLinkClicked(ev) { + const url = new URL(ev.target.href); + getResults(url); + ev.preventDefault(); +} + +function addPaginationEventListeners() { + pagination.addEventListener("click", paginationLinkClicked); +} + +function renderFragmentHTML(html) { + const fragment = range.createContextualFragment( + escapePolicy !== null ? escapePolicy.createHTML(html) : html, + ); + results = fragment.querySelector("#results"); + pagination = fragment.querySelector("#pagination"); + range.deleteContents(); + range.insertNode(fragment); + addToggleEventListeners(results); + addPaginationEventListeners(pagination); +} + +function getResults(url) { fetch(url, { headers: { fetch: "true", }, }) .then(async function (res) { - state.url = url.toJSON(); - state.opened = []; if (res.ok) { - history.pushState(state, null, url); return res.text(); } else { throw new Error(`${res.status} ${res.statusText}: ${await res.text()}`); } }) .then(function (html) { - const fragment = range.createContextualFragment( - escapePolicy !== null ? escapePolicy.createHTML(html) : html, - ); - const results = fragment.firstElementChild; - range.deleteContents(); - range.insertNode(results); - addToggleEventListeners(results); + state.url = url.toJSON(); + state.opened = []; + state.fragment = html; + history.pushState(state, null, url); + return renderFragmentHTML(html); }) .catch(function (error) { range.deleteContents(); range.insertNode(new Text(error.message)); console.error("fetch failed", error); }); +} + +search.addEventListener("submit", function (ev) { + const url = new URL(this.action); + url.search = new URLSearchParams(new FormData(this)).toString(); + getResults(url); ev.preventDefault(); }); if (results !== null) { addToggleEventListeners(results); } +if (pagination !== null) { + addPaginationEventListeners(pagination); +} if (state.opened.length > 0) { state.opened.forEach((id) => @@ -83,8 +110,13 @@ if (state.opened.length > 0) { } addEventListener("popstate", function (ev) { - if (ev.state == null || ev.state.url.pathname.startsWith("/search/")) { - range.deleteContents(); - search.reset(); + if (ev.state != null) { + url = new URL(ev.state.url); + if (!url.pathname.endsWith("/search") && ev.state.fragment !== null) { + renderFragmentHTML(ev.state.fragment); + return; + } } + range.deleteContents(); + search.reset(); }); diff --git a/frontend/static/style.css b/frontend/static/style.css index 375883e..dabf39c 100644 --- a/frontend/static/style.css +++ b/frontend/static/style.css @@ -41,7 +41,7 @@ body > header { } form { - margin-top: 1.5rem; + margin: 1.5rem 0; } fieldset { @@ -49,6 +49,13 @@ fieldset { column-gap: 1ex; border: none; padding: unset; + margin: unset; +} + +section { + border-top: none; + margin: unset; + padding: unset; } input[type="search"] { @@ -83,3 +90,17 @@ pre { pre:has(> code) { background: var(--bg); } + +section { + nav { + display: flex; + justify-content: space-between; + align-items: baseline; + a[rel="next"] { + margin-left: auto; + } + } + footer { + text-align: center; + } +} diff --git a/frontend/templates/blocks/options.gotmpl b/frontend/templates/blocks/options.gotmpl index e39d60c..88553b2 100644 --- a/frontend/templates/blocks/options.gotmpl +++ b/frontend/templates/blocks/options.gotmpl @@ -1,8 +1,8 @@ -{{- template "results" .Results -}} +{{- template "results" . -}} {{- define "results" }} - <div id="results"> - {{- with . }} - {{- range .Results }} + {{- if gt .Results.Total 0 }} + <section id="results"> + {{- range .Results.Results }} <details id="{{ .Option }}"> <summary> {{ .Option }} @@ -53,9 +53,20 @@ {{- end }} </dl> </details> - {{- else }} - Nothing found {{- end }} - {{- end }} - </div> + <footer> + <nav id="pagination"> + {{- with .Prev }} + <a class="button" href="{{ . }}" rel="prev">Prev</a> + {{- end }} + {{- with .Next }} + <a class="button" href="{{ . }}" rel="next">Next</a> + {{- end }} + </nav> + <span> {{ .Results.Total }} results </span> + </footer> + </section> + {{- else }} + Nothing found + {{- end }} {{- end }} diff --git a/frontend/templates/blocks/search.gotmpl b/frontend/templates/blocks/search.gotmpl index a4f0ee5..0be63c1 100644 --- a/frontend/templates/blocks/search.gotmpl +++ b/frontend/templates/blocks/search.gotmpl @@ -9,7 +9,7 @@ <button>Search</button> </fieldset> </form> - {{- with .Results }} + {{- if .Results }} {{ block "results" . }}{{ end }} {{- end }} {{- end }} |