|
|
@@ -69,13 +69,40 @@ function cell(r, k) {
|
|
|
return r[k];
|
|
|
}
|
|
|
|
|
|
-const S = { q: "", group: "mq", sort: "name", dir: 1, hidden: new Set(["title", "image", "port"]),
|
|
|
- page: 0, size: 50, closed: new Set(), openRow: null };
|
|
|
-// the browser restores form controls across page reloads — follow them
|
|
|
-// (otherwise the select shows the old choice while the table stays grouped
|
|
|
-// by the hardcoded default)
|
|
|
-S.q = document.getElementById("q").value;
|
|
|
-S.group = document.getElementById("group").value;
|
|
|
+// deep-link state: the full view (filter, grouping, sort, columns, paging,
|
|
|
+// collapsed groups) lives in the URL — survives any reload and is shareable
|
|
|
+const P = new URLSearchParams(location.search);
|
|
|
+const size0 = +(P.get("size") ?? 50);
|
|
|
+const S = {
|
|
|
+ q: P.get("q") ?? "",
|
|
|
+ group: P.get("group") ?? "mq",
|
|
|
+ sort: P.get("sort") ?? "name",
|
|
|
+ dir: P.get("dir") ? +P.get("dir") : 1,
|
|
|
+ size: [25, 50, 100, 0].includes(size0) ? size0 : 50,
|
|
|
+ page: Math.max(0, +(P.get("page") ?? 0)),
|
|
|
+ hidden: new Set((P.get("hidden") ?? "title,image,port").split(",").filter(Boolean)),
|
|
|
+ closed: new Set((P.get("closed") ?? "").split("|").map(decodeURIComponent).filter(Boolean)),
|
|
|
+ openRow: P.get("open") ?? null,
|
|
|
+};
|
|
|
+document.getElementById("q").value = S.q;
|
|
|
+document.getElementById("group").value = S.group;
|
|
|
+document.getElementById("size").value = String(S.size);
|
|
|
+function syncUrl() {
|
|
|
+ const p = new URLSearchParams();
|
|
|
+ if (S.q) p.set("q", S.q);
|
|
|
+ if (S.group !== "mq") p.set("group", S.group);
|
|
|
+ if (S.sort !== "name") p.set("sort", S.sort);
|
|
|
+ if (S.dir !== 1) p.set("dir", String(S.dir));
|
|
|
+ if (S.size !== 50) p.set("size", String(S.size));
|
|
|
+ if (S.page) p.set("page", String(S.page));
|
|
|
+ const hidden = [...S.hidden].sort();
|
|
|
+ if (hidden.join(",") !== ["title", "image", "port"].sort().join(",")) p.set("hidden", hidden.join(","));
|
|
|
+ const closed = [...S.closed].map(encodeURIComponent);
|
|
|
+ if (closed.length) p.set("closed", closed.join("|"));
|
|
|
+ if (S.openRow) p.set("open", S.openRow);
|
|
|
+ const qs = p.toString();
|
|
|
+ history.replaceState(null, "", qs ? "?" + qs : location.pathname);
|
|
|
+}
|
|
|
|
|
|
const esc = s => String(s).replace(/[&<>"]/g, c => ({"&":"&","<":"<",">":">",'"':"""}[c]));
|
|
|
|
|
|
@@ -108,6 +135,7 @@ function gkey(r, level) {
|
|
|
}
|
|
|
|
|
|
function render() {
|
|
|
+ syncUrl();
|
|
|
// visible columns
|
|
|
const vis = COLS.filter(c => !S.hidden.has(c[0]));
|
|
|
document.querySelector("thead").innerHTML = "<tr>" + vis.map(c => {
|