about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--frontend/static/search.js56
-rw-r--r--frontend/templates/blocks/options.gotmpl2
2 files changed, 54 insertions, 4 deletions
diff --git a/frontend/static/search.js b/frontend/static/search.js
index 20f1a7d..5bbc374 100644
--- a/frontend/static/search.js
+++ b/frontend/static/search.js
@@ -1,15 +1,47 @@
 const search = document.getElementById("search");
 const results = document.getElementById("results");
+let state = history.state || {
+  url: new URL(location).toJSON(),
+  opened: [],
+};
+
+let escapePolicy = null;
+if (window.trustedTypes && trustedTypes.createPolicy) {
+  escapePolicy = trustedTypes.createPolicy("fetch", {
+    createHTML: (string, sink) => string,
+  });
+}
+
+function detailsToggled(ev) {
+  const nextURL = new URL(location);
+  if (ev.newState == "open" || ev.target.open === true) {
+    state.opened.push(this.id);
+    nextURL.hash = this.id;
+  } else {
+    state.opened = state.opened.filter((x) => x != this.id);
+    nextURL.hash = "";
+  }
+  state.url = nextURL.toJSON();
+  history.replaceState(state, "", nextURL);
+}
+function addToggleEventListeners() {
+  results.querySelectorAll("details").forEach((details) =>
+    // toggle event doesn't bubble :(
+    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();
-  const res = fetch(url, {
+  fetch(url, {
     headers: {
       fetch: "true",
     },
   })
     .then(function (res) {
-      window.history.pushState(null, null, url);
+      state.url = url.toJSON();
+      state.opened = [];
+      history.pushState(state, null, url);
       if (res.ok) {
         return res.text();
       } else {
@@ -17,10 +49,28 @@ search.addEventListener("submit", function (ev) {
       }
     })
     .then(function (html) {
-      results.innerHTML = html;
+      results.innerHTML =
+        escapePolicy !== null ? escapePolicy.createHTML(html) : html;
+      addToggleEventListeners();
     })
     .catch(function (error) {
       console.error("fetch failed", error);
     });
   ev.preventDefault();
 });
+
+addToggleEventListeners();
+
+if (state.opened.length > 0) {
+  state.opened.forEach((id) =>
+    document.getElementById(id).setAttribute("open", "open"),
+  );
+} else if (location.hash) {
+  document.getElementById(location.hash.slice(1)).setAttribute("open", "open");
+}
+
+addEventListener("popstate", function (ev) {
+  if (ev.state == null || ev.state.url.pathname == "/") {
+    results.replaceChildren();
+  }
+});
diff --git a/frontend/templates/blocks/options.gotmpl b/frontend/templates/blocks/options.gotmpl
index e67a5c1..d5171c3 100644
--- a/frontend/templates/blocks/options.gotmpl
+++ b/frontend/templates/blocks/options.gotmpl
@@ -1,6 +1,6 @@
 {{ define "results" }}
   {{- range $opt, $data := .Results }}
-    <details>
+    <details id="{{ $opt }}">
       <summary>
         {{ $opt }}
       </summary>