admin: tab system improvement, search: ?search qp

tab content classes (e.g. settingsList, activityList)
can implement "AsTab", "Navigatable" and or "PageEventBindable",
the first giving them a tab name, a subpath and a reloader function,
the second an "isURL" and "navigate" function for loading resources,
the last giving them bind/unbindPageEvent methods. These are looped
through in ts/admin.ts still crudely, maybe tabs.ts could accept "AsTab"
implementers directly.

"Search" class now has a ?search query param which just encodes the
search box content, set when you perform a server search (hit enter or
press the button). ?user queries from the accounts or activity tab will
be converted to this form on loading.
This commit is contained in:
Harvey Tindall
2025-12-24 12:56:02 +00:00
parent 748acc13c0
commit 3308739619
10 changed files with 145 additions and 129 deletions
+37 -11
View File
@@ -68,9 +68,11 @@ export abstract class Query {
protected _subject: QueryType;
protected _operator: QueryOperator;
protected _card: HTMLElement;
protected _id: string;
public type: string;
constructor(subject: QueryType | null, operator: QueryOperator) {
constructor(id: string, subject: QueryType | null, operator: QueryOperator) {
this._id = id;
this._subject = subject;
this._operator = operator;
if (subject != null) {
@@ -116,8 +118,8 @@ export abstract class Query {
export class BoolQuery extends Query {
protected _value: boolean;
constructor(subject: QueryType, value: boolean) {
super(subject, QueryOperator.Equal);
constructor(id: string, subject: QueryType, value: boolean) {
super(id, subject, QueryOperator.Equal);
this.type = "bool";
this._value = value;
this._card.classList.add("button", "@high", "center", "flex", "flex-row", "gap-2");
@@ -165,8 +167,8 @@ export class BoolQuery extends Query {
export class StringQuery extends Query {
protected _value: string;
constructor(subject: QueryType, value: string) {
super(subject, QueryOperator.Equal);
constructor(id: string, subject: QueryType, value: string) {
super(id, subject, QueryOperator.Equal);
this.type = "string";
this._value = value.toLowerCase();
this._card.classList.add("button", "~neutral", "@low", "center", "flex", "flex-row", "gap-2");
@@ -214,8 +216,8 @@ const dateSetters: Map<string, (v: number) => void> = (() => {
export class DateQuery extends Query {
protected _value: ParsedDate;
constructor(subject: QueryType, operator: QueryOperator, value: ParsedDate) {
super(subject, operator);
constructor(id: string, subject: QueryType, operator: QueryOperator, value: ParsedDate) {
super(id, subject, operator);
this.type = "date";
this._value = value;
this._card.classList.add("button", "~neutral", "@low", "center", "flex", "flex-row", "gap-2");
@@ -278,7 +280,7 @@ export const SearchableItemDataAttribute = "data-search-item";
export type SearchableItems = Map<string, SearchableItem>;
export class Search {
export class Search implements Navigatable {
private _c: SearchConfiguration;
private _sortField: string = "";
private _ascending: boolean = true;
@@ -368,7 +370,7 @@ export class Search {
if (queryFormat.bool) {
let [boolState, isBool] = BoolQuery.paramsFromString(split[1]);
if (isBool) {
q = new BoolQuery(queryFormat, boolState);
q = new BoolQuery(split[0], queryFormat, boolState);
q.onclick = () => {
for (let quote of [`"`, `'`, ``]) {
this._c.search.value = this._c.search.value.replace(
@@ -383,7 +385,7 @@ export class Search {
}
}
if (queryFormat.string) {
q = new StringQuery(queryFormat, split[1]);
q = new StringQuery(split[0], queryFormat, split[1]);
q.onclick = () => {
for (let quote of [`"`, `'`, ``]) {
@@ -398,7 +400,7 @@ export class Search {
if (queryFormat.date) {
let [parsedDate, op, isDate] = DateQuery.paramsFromString(split[1]);
if (!isDate) continue;
q = new DateQuery(queryFormat, op, parsedDate);
q = new DateQuery(split[0], queryFormat, op, parsedDate);
q.onclick = () => {
for (let quote of [`"`, `'`, ``]) {
@@ -695,6 +697,7 @@ export class Search {
onServerSearch = () => {
const newServerSearch = !this.inServerSearch;
this.inServerSearch = true;
this.setQueryParam();
this.searchServer(newServerSearch);
};
@@ -718,10 +721,33 @@ export class Search {
return req;
};
// setQueryParam sets the ?search query param to the current searchbox content.
setQueryParam = () => {
const url = new URL(window.location.href);
// FIXME: do better and make someone else clear this
url.searchParams.delete("user");
url.searchParams.set("search", this._c.search.value);
window.history.pushState(null, "", url.toString());
};
setServerSearchButtonsDisabled = (disabled: boolean) => {
this._serverSearchButtons.forEach((v: HTMLButtonElement) => (v.disabled = disabled));
};
isURL = (url?: string) => {
const urlParams = new URLSearchParams(url || window.location.search);
const searchContent = urlParams.get("search");
return Boolean(searchContent);
};
navigate = (url?: string) => {
const urlParams = new URLSearchParams(url || window.location.search);
const searchContent = urlParams.get("search");
this._c.search.value = searchContent;
this.onSearchBoxChange();
this.onServerSearch();
};
constructor(c: SearchConfiguration) {
this._c = c;
if (!this._c.search) {