about summary refs log tree commit diff stats
path: root/frontend
diff options
context:
space:
mode:
Diffstat (limited to 'frontend')
-rw-r--r--frontend/static/search.js66
-rw-r--r--frontend/static/style.css23
-rw-r--r--frontend/templates/blocks/options.gotmpl27
-rw-r--r--frontend/templates/blocks/search.gotmpl2
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 }}