diff --git a/ts/modules/accounts.ts b/ts/modules/accounts.ts
index 34595ad..cb1c757 100644
--- a/ts/modules/accounts.ts
+++ b/ts/modules/accounts.ts
@@ -3,6 +3,7 @@ import { templateEmail } from "../modules/settings.js";
import { Marked } from "@ts-stack/markdown";
import { stripMarkdown } from "../modules/stripmd.js";
import { DiscordUser, newDiscordSearch } from "../modules/discord.js";
+import { Search, SearchConfiguration, QueryType, SearchableItem } from "../modules/search.js";
const dateParser = require("any-date-parser");
interface User {
@@ -39,7 +40,7 @@ interface announcementTemplate {
var addDiscord: (passData: string) => void;
-class user implements User {
+class user implements User, SearchableItem {
private _id = "";
private _row: HTMLTableRowElement;
private _check: HTMLInputElement;
@@ -269,7 +270,7 @@ class user implements User {
- `;
+ `;
(this._matrix.querySelector("span") as HTMLSpanElement).onclick = this._addMatrix;
} else {
this._notifyDropdown.querySelector(".accounts-area-matrix").classList.remove("unfocused");
@@ -780,13 +781,13 @@ export class accountsList {
private _userSelect = document.getElementById("modify-user-users") as HTMLSelectElement;
private _referralsProfileSelect = document.getElementById("enable-referrals-user-profiles") as HTMLSelectElement;
private _referralsInviteSelect = document.getElementById("enable-referrals-user-invites") as HTMLSelectElement;
- private _search = document.getElementById("accounts-search") as HTMLInputElement;
+ private _searchBox = document.getElementById("accounts-search") as HTMLInputElement;
+ private _search: Search;
private _selectAll = document.getElementById("accounts-select-all") as HTMLInputElement;
private _users: { [id: string]: user };
private _ordering: string[] = [];
private _checkCount: number = 0;
- private _inSearch = false;
// Whether the enable/disable button should enable or not.
private _shouldEnable = false;
@@ -836,7 +837,7 @@ export class accountsList {
}
}
- private _queries: { [field: string]: { name: string, getter: string, bool: boolean, string: boolean, date: boolean, dependsOnTableHeader?: string, show?: boolean }} = {
+ private _queries: { [field: string]: QueryType } = {
"id": {
// We don't use a translation here to circumvent the name substitution feature.
name: "Jellyfin/Emby ID",
@@ -887,7 +888,7 @@ export class accountsList {
bool: true,
string: false,
date: false,
- dependsOnTableHeader: "accounts-header-access-jfa"
+ dependsOnElement: ".accounts-header-access-jfa"
},
"email": {
name: window.lang.strings("emailAddress"),
@@ -895,7 +896,7 @@ export class accountsList {
bool: true,
string: true,
date: false,
- dependsOnTableHeader: "accounts-header-email"
+ dependsOnElement: ".accounts-header-email"
},
"telegram": {
name: "Telegram",
@@ -903,7 +904,7 @@ export class accountsList {
bool: true,
string: true,
date: false,
- dependsOnTableHeader: "accounts-header-telegram"
+ dependsOnElement: ".accounts-header-telegram"
},
"matrix": {
name: "Matrix",
@@ -911,7 +912,7 @@ export class accountsList {
bool: true,
string: true,
date: false,
- dependsOnTableHeader: "accounts-header-matrix"
+ dependsOnElement: ".accounts-header-matrix"
},
"discord": {
name: "Discord",
@@ -919,7 +920,7 @@ export class accountsList {
bool: true,
string: true,
date: false,
- dependsOnTableHeader: "accounts-header-discord"
+ dependsOnElement: ".accounts-header-discord"
},
"expiry": {
name: window.lang.strings("expiry"),
@@ -927,7 +928,7 @@ export class accountsList {
bool: true,
string: false,
date: true,
- dependsOnTableHeader: "accounts-header-expiry"
+ dependsOnElement: ".accounts-header-expiry"
},
"last-active": {
name: window.lang.strings("lastActiveTime"),
@@ -942,229 +943,12 @@ export class accountsList {
bool: true,
string: false,
date: false,
- dependsOnTableHeader: "accounts-header-referrals"
+ dependsOnElement: ".accounts-header-referrals"
}
}
private _notFoundPanel: HTMLElement = document.getElementById("accounts-not-found");
- search = (query: String): string[] => {
- console.log(this._queries);
- this._filterArea.textContent = "";
-
- query = query.toLowerCase();
- let result: string[] = [...this._ordering];
- // console.log("initial:", result);
-
- // const words = query.split(" ");
- let words: string[] = [];
-
- let quoteSymbol = ``;
- let queryStart = -1;
- let lastQuote = -1;
- for (let i = 0; i < query.length; i++) {
- if (queryStart == -1 && query[i] != " " && query[i] != `"` && query[i] != `'`) {
- queryStart = i;
- }
- if ((query[i] == `"` || query[i] == `'`) && (quoteSymbol == `` || query[i] == quoteSymbol)) {
- if (lastQuote != -1) {
- lastQuote = -1;
- quoteSymbol = ``;
- } else {
- lastQuote = i;
- quoteSymbol = query[i];
- }
- }
-
- if (query[i] == " " || i == query.length-1) {
- if (lastQuote != -1) {
- continue;
- } else {
- let end = i+1;
- if (query[i] == " ") {
- end = i;
- while (i+1 < query.length && query[i+1] == " ") {
- i += 1;
- }
- }
- words.push(query.substring(queryStart, end).replace(/['"]/g, ""));
- console.log("pushed", words);
- queryStart = -1;
- }
- }
- }
-
- query = "";
- for (let word of words) {
- if (!word.includes(":")) {
- let cachedResult = [...result];
- for (let id of cachedResult) {
- const u = this._users[id];
- if (!u.matchesSearch(word)) {
- result.splice(result.indexOf(id), 1);
- }
- }
- continue;
- }
- const split = [word.substring(0, word.indexOf(":")), word.substring(word.indexOf(":")+1)];
-
- if (!(split[0] in this._queries)) continue;
-
- const queryFormat = this._queries[split[0]];
-
- if (queryFormat.bool) {
- let isBool = false;
- let boolState = false;
- if (split[1] == "true" || split[1] == "yes" || split[1] == "t" || split[1] == "y") {
- isBool = true;
- boolState = true;
- } else if (split[1] == "false" || split[1] == "no" || split[1] == "f" || split[1] == "n") {
- isBool = true;
- boolState = false;
- }
- if (isBool) {
- const filterCard = document.createElement("span");
- filterCard.ariaLabel = window.lang.strings("clickToRemoveFilter");
- filterCard.classList.add("button", "~" + (boolState ? "positive" : "critical"), "@high", "center", "mx-2", "h-full");
- filterCard.innerHTML = `
- ${queryFormat.name}
-
- `;
-
- filterCard.addEventListener("click", () => {
- for (let quote of [`"`, `'`, ``]) {
- this._search.value = this._search.value.replace(split[0] + ":" + quote + split[1] + quote, "");
- }
- this._search.oninput((null as Event));
- })
-
- this._filterArea.appendChild(filterCard);
-
- // console.log("is bool, state", boolState);
- // So removing elements doesn't affect us
- let cachedResult = [...result];
- for (let id of cachedResult) {
- const u = this._users[id];
- const value = Object.getOwnPropertyDescriptor(user.prototype, queryFormat.getter).get.call(u);
- // console.log("got", queryFormat.getter + ":", value);
- // Remove from result if not matching query
- if (!((value && boolState) || (!value && !boolState))) {
- // console.log("not matching, result is", result);
- result.splice(result.indexOf(id), 1);
- }
- }
- continue
- }
- }
- if (queryFormat.string) {
- const filterCard = document.createElement("span");
- filterCard.ariaLabel = window.lang.strings("clickToRemoveFilter");
- filterCard.classList.add("button", "~neutral", "@low", "center", "mx-2", "h-full");
- filterCard.innerHTML = `
- ${queryFormat.name}: "${split[1]}"
- `;
-
- filterCard.addEventListener("click", () => {
- for (let quote of [`"`, `'`, ``]) {
- let regex = new RegExp(split[0] + ":" + quote + split[1] + quote, "ig");
- this._search.value = this._search.value.replace(regex, "");
- }
- this._search.oninput((null as Event));
- })
-
- this._filterArea.appendChild(filterCard);
-
- let cachedResult = [...result];
- for (let id of cachedResult) {
- const u = this._users[id];
- const value = Object.getOwnPropertyDescriptor(user.prototype, queryFormat.getter).get.call(u);
- if (!(value.includes(split[1]))) {
- result.splice(result.indexOf(id), 1);
- }
- }
- continue;
- }
- if (queryFormat.date) {
- // -1 = Before, 0 = On, 1 = After, 2 = No symbol, assume 0
- let compareType = (split[1][0] == ">") ? 1 : ((split[1][0] == "<") ? -1 : ((split[1][0] == "=") ? 0 : 2));
- let unmodifiedValue = split[1];
- if (compareType != 2) {
- split[1] = split[1].substring(1);
- }
- if (compareType == 2) compareType = 0;
-
- let attempt: { year?: number, month?: number, day?: number, hour?: number, minute?: number } = dateParser.attempt(split[1]);
- // Month in Date objects is 0-based, so make our parsed date that way too
- if ("month" in attempt) attempt.month -= 1;
-
- let date: Date = (Date as any).fromString(split[1]) as Date;
- console.log("Read", attempt, "and", date);
- if ("invalid" in (date as any)) continue;
-
- const filterCard = document.createElement("span");
- filterCard.ariaLabel = window.lang.strings("clickToRemoveFilter");
- filterCard.classList.add("button", "~neutral", "@low", "center", "m-2", "h-full");
- filterCard.innerHTML = `
- ${queryFormat.name}: ${(compareType == 1) ? window.lang.strings("after")+" " : ((compareType == -1) ? window.lang.strings("before")+" " : "")}${split[1]}
- `;
-
- filterCard.addEventListener("click", () => {
- for (let quote of [`"`, `'`, ``]) {
- let regex = new RegExp(split[0] + ":" + quote + unmodifiedValue + quote, "ig");
- this._search.value = this._search.value.replace(regex, "");
- }
-
- this._search.oninput((null as Event));
- })
-
- this._filterArea.appendChild(filterCard);
-
- let cachedResult = [...result];
- for (let id of cachedResult) {
- const u = this._users[id];
- const unixValue = Object.getOwnPropertyDescriptor(user.prototype, queryFormat.getter).get.call(u);
- if (unixValue == 0) {
- result.splice(result.indexOf(id), 1);
- continue;
- }
- let value = new Date(unixValue*1000);
-
- const getterPairs: [string, () => number][] = [["year", Date.prototype.getFullYear], ["month", Date.prototype.getMonth], ["day", Date.prototype.getDate], ["hour", Date.prototype.getHours], ["minute", Date.prototype.getMinutes]];
-
- // When doing > or <