From 87ec9ecf21781c5289257750fd41c6b9991f1f6e Mon Sep 17 00:00:00 2001 From: Alan Pearce Date: Sat, 4 May 2024 19:50:26 +0200 Subject: feat: browser history management --- frontend/static/search.js | 56 ++++++++++++++++++++++++++++++-- frontend/templates/blocks/options.gotmpl | 2 +- 2 files changed, 54 insertions(+), 4 deletions(-) (limited to 'frontend') 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 }} -
+
{{ $opt }} -- cgit 1.4.1