about summary refs log tree commit diff stats
path: root/frontend/static/search.js
diff options
context:
space:
mode:
authorAlan Pearce2024-05-08 00:15:52 +0200
committerAlan Pearce2024-05-08 00:16:03 +0200
commit973345ad50f9b237714fcb364cf7f665b3909f9d (patch)
tree15225430bd5895b5140df0e301b0e6c3fb5758a8 /frontend/static/search.js
parentf459e84ecf7307fe2eeb7fbaa5b0c50613ec04f4 (diff)
downloadsearchix-973345ad50f9b237714fcb364cf7f665b3909f9d.tar.lz
searchix-973345ad50f9b237714fcb364cf7f665b3909f9d.tar.zst
searchix-973345ad50f9b237714fcb364cf7f665b3909f9d.zip
feat: paginate search results
Diffstat (limited to 'frontend/static/search.js')
-rw-r--r--frontend/static/search.js66
1 files changed, 49 insertions, 17 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();
 });