mirror of
https://github.com/hrfee/jfa-go.git
synced 2024-12-22 17:10:10 +00:00
activity: functional search (client-side)
search with filters for each type of card, and all the info in them. Gonna somehow need to figure out what to do about pagination.
This commit is contained in:
parent
4fa0630aef
commit
a0db685af2
@ -725,48 +725,20 @@
|
|||||||
<div class="flex-expand align-middle">
|
<div class="flex-expand align-middle">
|
||||||
<span class="text-3xl font-bold mr-4">{{ .strings.activity }}</span>
|
<span class="text-3xl font-bold mr-4">{{ .strings.activity }}</span>
|
||||||
<div id="activity-filter-dropdown" class="dropdown z-10" tabindex="0">
|
<div id="activity-filter-dropdown" class="dropdown z-10" tabindex="0">
|
||||||
<span class="h-100 button ~neutral @low center" id="accounts-filter-button">{{ .strings.filters }}</span>
|
<span class="h-100 button ~neutral @low center" id="activity-filter-button">{{ .strings.filters }}</span>
|
||||||
<div class="dropdown-display">
|
<div class="dropdown-display">
|
||||||
<div class="card ~neutral @low mt-2" id="accounts-filter-list">
|
<div class="card ~neutral @low mt-2" id="activity-filter-list">
|
||||||
<p class="supra pb-2">{{ .strings.filters }}</p>
|
<p class="supra pb-2">{{ .strings.filters }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<input type="search" class="field ~neutral @low input search ml-2 mr-2" id="accounts-search" placeholder="{{ .strings.search }}">
|
<input type="search" class="field ~neutral @low input search ml-2 mr-2" id="activity-search" placeholder="{{ .strings.search }}">
|
||||||
<span class="button ~neutral @low center ml-[-2.64rem] rounded-s-none accounts-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
|
<span class="button ~neutral @low center ml-[-2.64rem] rounded-s-none activity-search-clear" aria-label="{{ .strings.clearSearch }}" text="{{ .strings.clearSearch }}"><i class="ri-close-line"></i></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="supra py-1 sm hidden" id="accounts-search-options-header">{{ .strings.searchOptions }}</div>
|
<div class="supra py-1 sm hidden" id="activity-search-options-header">{{ .strings.searchOptions }}</div>
|
||||||
<div class="row -mx-2 mb-2">
|
<div class="row -mx-2 mb-2">
|
||||||
<button type="button" class="button ~neutral @low center mx-2 hidden"><span id="accounts-sort-by-field"></span> <i class="ri-close-line ml-2 text-2xl"></i></button>
|
<button type="button" class="button ~neutral @low center mx-2 hidden"><span id="activity-sort-by-field"></span> <i class="ri-close-line ml-2 text-2xl"></i></button>
|
||||||
<span id="accounts-filter-area"></span>
|
<span id="activity-filter-area"></span>
|
||||||
</div>
|
|
||||||
<div class="supra py-1 sm">{{ .strings.actions }}</div>
|
|
||||||
<div class="row -mx-2">
|
|
||||||
<span class="col button ~neutral @low center max-w-[20%]" id="accounts-add-user">{{ .quantityStrings.addUser.Singular }}</span>
|
|
||||||
<div id="accounts-announce-dropdown" class="col dropdown pb-0i max-w-[20%]" tabindex="0">
|
|
||||||
<span class="w-100 button ~info @low center" id="accounts-announce">{{ .strings.announce }}</span>
|
|
||||||
<div class="dropdown-display">
|
|
||||||
<div class="card ~neutral @low">
|
|
||||||
<span class="supra sm">{{ .strings.templates }}</span>
|
|
||||||
<div id="accounts-announce-templates"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span class="col button ~urge @low center max-w-[20%]" id="accounts-modify-user">{{ .strings.modifySettings }}</span>
|
|
||||||
{{ if .referralsEnabled }}
|
|
||||||
<span class="col button ~urge @low center max-w-[20%]" id="accounts-enable-referrals">{{ .strings.enableReferrals }}</span>
|
|
||||||
{{ end }}
|
|
||||||
<span class="col button ~warning @low center max-w-[20%]" id="accounts-extend-expiry">{{ .strings.extendExpiry }}</span>
|
|
||||||
<div id="accounts-disable-enable-dropdown" class="col dropdown manual pb-0i max-w-[20%]" tabindex="0">
|
|
||||||
<span class="w-100 button ~positive @low center" id="accounts-disable-enable">{{ .strings.disable }}</span>
|
|
||||||
<div class="dropdown-display">
|
|
||||||
<div class="card ~neutral @low">
|
|
||||||
<span class="button ~urge full-width accounts-announce-template-button" id="accounts-enable-expiry">{{ .strings.setExpiry }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<span class="col button ~info @low center unfocused max-w-[20%]" id="accounts-send-pwr">{{ .strings.sendPWR }}</span>
|
|
||||||
<span class="col button ~critical @low center max-w-[20%]" id="accounts-delete-user">{{ .quantityStrings.deleteUser.Singular }}</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col md:flex-row gap-3">
|
<div class="flex flex-col md:flex-row gap-3">
|
||||||
<div class="card @low dark:~d_neutral col max-w-[20%]">
|
<div class="card @low dark:~d_neutral col max-w-[20%]">
|
||||||
@ -778,19 +750,14 @@
|
|||||||
<span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-about"><span class="flex">{{ .strings.aboutProgram }} <i class="ri-information-line ml-2"></i></span></span>
|
<span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-about"><span class="flex">{{ .strings.aboutProgram }} <i class="ri-information-line ml-2"></i></span></span>
|
||||||
<span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-profiles"><span class="flex">{{ .strings.userProfiles }} <i class="ri-user-line ml-2"></i></span></span>
|
<span class="button ~neutral @low settings-section-button justify-between mb-2" id="setting-profiles"><span class="flex">{{ .strings.userProfiles }} <i class="ri-user-line ml-2"></i></span></span>
|
||||||
</div>
|
</div>
|
||||||
<div class="card ~neutral @low col" id="activity-card-list">
|
<div class="card ~neutral @low col">
|
||||||
<div class="card ~urge @low">
|
<div id="activity-card-list"></div>
|
||||||
<div class="flex justify-between mb-2">
|
<div class="unfocused h-[100%] my-3" id="activity-not-found">
|
||||||
<span class="heading text-2xl activity-title">Account Created: <a href="/fixme" class="activity-url">"hrfee"</a></span>
|
<div class="flex flex-col h-[100%] justify-center items-center">
|
||||||
<span class="text-sm font-medium activity-time">26/10/23 14:32</span>
|
<span class="text-2xl font-medium italic mb-3">{{ .strings.noResultsFound }}</span>
|
||||||
</div>
|
<button class="button ~neutral @low activity-search-clear">
|
||||||
<div class="flex justify-between">
|
<span class="mr-2">{{ .strings.clearSearch }}</span><i class="ri-close-line"></i>
|
||||||
<div>
|
</button>
|
||||||
<span class="content supra mr-2 activity-source-type">From Invite</span><a href="fixme" class="activity-source">BdBmpGDzuJhHSsboAsYgrE</a>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<span class="content supra mr-2">Referrer</span><a href="fixme" class="activity-referrer">username</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -151,7 +151,22 @@
|
|||||||
"fromInvite": "From Invite",
|
"fromInvite": "From Invite",
|
||||||
"byAdmin": "By Admin",
|
"byAdmin": "By Admin",
|
||||||
"byUser": "By User",
|
"byUser": "By User",
|
||||||
"byJfaGo": "By jfa-go"
|
"byJfaGo": "By jfa-go",
|
||||||
|
"activityID": "Activity ID",
|
||||||
|
"title": "Title",
|
||||||
|
"usersMentioned": "User mentioned",
|
||||||
|
"actor": "Actor",
|
||||||
|
"actorDescription": "The thing that caused this action. <hr class=\"sep\"> \"user\"/\"admin\"/\"daemon\" or a username.",
|
||||||
|
"accountCreationFilter": "Account Creation",
|
||||||
|
"accountDeletionFilter": "Account Deletion",
|
||||||
|
"accountDisabledFilter": "Account Disabled",
|
||||||
|
"accountEnabledFilter": "Account Enabled",
|
||||||
|
"contactLinkedFilter": "Contact Linked",
|
||||||
|
"contactUnlinkedFilter": "Contact Unlinked",
|
||||||
|
"passwordChangeFilter": "Password Changed",
|
||||||
|
"passwordResetFilter": "Password Reset",
|
||||||
|
"inviteCreatedFilter": "Invite Created",
|
||||||
|
"inviteDeletedFilter": "Invite Deleted/Expired"
|
||||||
},
|
},
|
||||||
"notifications": {
|
"notifications": {
|
||||||
"changedEmailAddress": "Changed email address of {n}.",
|
"changedEmailAddress": "Changed email address of {n}.",
|
||||||
|
@ -1803,6 +1803,7 @@ export class accountsList {
|
|||||||
sortingByButton: this._sortingByButton,
|
sortingByButton: this._sortingByButton,
|
||||||
searchOptionsHeader: this._searchOptionsHeader,
|
searchOptionsHeader: this._searchOptionsHeader,
|
||||||
notFoundPanel: this._notFoundPanel,
|
notFoundPanel: this._notFoundPanel,
|
||||||
|
filterList: document.getElementById("accounts-filter-list"),
|
||||||
search: this._searchBox,
|
search: this._searchBox,
|
||||||
queries: this._queries,
|
queries: this._queries,
|
||||||
setVisibility: this.setVisibility,
|
setVisibility: this.setVisibility,
|
||||||
@ -1883,84 +1884,7 @@ export class accountsList {
|
|||||||
defaultSort();
|
defaultSort();
|
||||||
this.showHideSearchOptionsHeader();
|
this.showHideSearchOptionsHeader();
|
||||||
|
|
||||||
const filterList = document.getElementById("accounts-filter-list");
|
this._search.generateFilterList();
|
||||||
|
|
||||||
const fillInFilter = (name: string, value: string, offset?: number) => {
|
|
||||||
this._searchBox.value = name + ":" + value + " " + this._searchBox.value;
|
|
||||||
this._searchBox.focus();
|
|
||||||
let newPos = name.length + 1 + value.length;
|
|
||||||
if (typeof offset !== 'undefined')
|
|
||||||
newPos += offset;
|
|
||||||
this._searchBox.setSelectionRange(newPos, newPos);
|
|
||||||
this._searchBox.oninput(null as any);
|
|
||||||
};
|
|
||||||
|
|
||||||
// Generate filter buttons
|
|
||||||
for (let queryName of Object.keys(this._queries)) {
|
|
||||||
const query = this._queries[queryName];
|
|
||||||
if ("show" in query && !query.show) continue;
|
|
||||||
if ("dependsOnElement" in query && query.dependsOnElement) {
|
|
||||||
const el = document.querySelector(query.dependsOnElement);
|
|
||||||
if (el === null) continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const container = document.createElement("span") as HTMLSpanElement;
|
|
||||||
container.classList.add("button", "button-xl", "~neutral", "@low", "mb-1", "mr-2");
|
|
||||||
container.innerHTML = `<span class="mr-2">${query.name}</span>`;
|
|
||||||
if (query.bool) {
|
|
||||||
const pos = document.createElement("button") as HTMLButtonElement;
|
|
||||||
pos.type = "button";
|
|
||||||
pos.ariaLabel = `Filter by "${query.name}": True`;
|
|
||||||
pos.classList.add("button", "~positive", "ml-2");
|
|
||||||
pos.innerHTML = `<i class="ri-checkbox-circle-fill"></i>`;
|
|
||||||
pos.addEventListener("click", () => fillInFilter(queryName, "true"));
|
|
||||||
const neg = document.createElement("button") as HTMLButtonElement;
|
|
||||||
neg.type = "button";
|
|
||||||
neg.ariaLabel = `Filter by "${query.name}": False`;
|
|
||||||
neg.classList.add("button", "~critical", "ml-2");
|
|
||||||
neg.innerHTML = `<i class="ri-close-circle-fill"></i>`;
|
|
||||||
neg.addEventListener("click", () => fillInFilter(queryName, "false"));
|
|
||||||
|
|
||||||
container.appendChild(pos);
|
|
||||||
container.appendChild(neg);
|
|
||||||
}
|
|
||||||
if (query.string) {
|
|
||||||
const button = document.createElement("button") as HTMLButtonElement;
|
|
||||||
button.type = "button";
|
|
||||||
button.classList.add("button", "~urge", "ml-2");
|
|
||||||
button.innerHTML = `<i class="ri-equal-line mr-2"></i>${window.lang.strings("matchText")}`;
|
|
||||||
|
|
||||||
// Position cursor between quotes
|
|
||||||
button.addEventListener("click", () => fillInFilter(queryName, `""`, -1));
|
|
||||||
|
|
||||||
container.appendChild(button);
|
|
||||||
}
|
|
||||||
if (query.date) {
|
|
||||||
const onDate = document.createElement("button") as HTMLButtonElement;
|
|
||||||
onDate.type = "button";
|
|
||||||
onDate.classList.add("button", "~urge", "ml-2");
|
|
||||||
onDate.innerHTML = `<i class="ri-calendar-check-line mr-2"></i>On Date`;
|
|
||||||
onDate.addEventListener("click", () => fillInFilter(queryName, `"="`, -1));
|
|
||||||
|
|
||||||
const beforeDate = document.createElement("button") as HTMLButtonElement;
|
|
||||||
beforeDate.type = "button";
|
|
||||||
beforeDate.classList.add("button", "~urge", "ml-2");
|
|
||||||
beforeDate.innerHTML = `<i class="ri-calendar-check-line mr-2"></i>Before Date`;
|
|
||||||
beforeDate.addEventListener("click", () => fillInFilter(queryName, `"<"`, -1));
|
|
||||||
|
|
||||||
const afterDate = document.createElement("button") as HTMLButtonElement;
|
|
||||||
afterDate.type = "button";
|
|
||||||
afterDate.classList.add("button", "~urge", "ml-2");
|
|
||||||
afterDate.innerHTML = `<i class="ri-calendar-check-line mr-2"></i>After Date`;
|
|
||||||
afterDate.addEventListener("click", () => fillInFilter(queryName, `">"`, -1));
|
|
||||||
|
|
||||||
container.appendChild(onDate);
|
|
||||||
container.appendChild(beforeDate);
|
|
||||||
container.appendChild(afterDate);
|
|
||||||
}
|
|
||||||
|
|
||||||
filterList.appendChild(container);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
reload = () => {
|
reload = () => {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { _post, _delete, toDateString } from "../modules/common.js";
|
import { _post, _delete, toDateString } from "../modules/common.js";
|
||||||
|
import { Search, SearchConfiguration, QueryType, SearchableItem } from "../modules/search.js";
|
||||||
|
|
||||||
export interface activity {
|
export interface activity {
|
||||||
id: string;
|
id: string;
|
||||||
@ -26,11 +27,11 @@ var activityTypeMoods = {
|
|||||||
"deleteInvite": -1
|
"deleteInvite": -1
|
||||||
};
|
};
|
||||||
|
|
||||||
var moodColours = ["~warning", "~neutral", "~urge"];
|
// var moodColours = ["~warning", "~neutral", "~urge"];
|
||||||
|
|
||||||
export var activityReload = new CustomEvent("activity-reload");
|
export var activityReload = new CustomEvent("activity-reload");
|
||||||
|
|
||||||
export class Activity { // FIXME: Add "implements"
|
export class Activity implements activity, SearchableItem { // FIXME: Add "implements"
|
||||||
private _card: HTMLElement;
|
private _card: HTMLElement;
|
||||||
private _title: HTMLElement;
|
private _title: HTMLElement;
|
||||||
private _time: HTMLElement;
|
private _time: HTMLElement;
|
||||||
@ -64,6 +65,33 @@ export class Activity { // FIXME: Add "implements"
|
|||||||
return `<a class="hover:underline" href="/accounts/invites/${this.invite_code}">${this._renderInvText()}</a>`;
|
return `<a class="hover:underline" href="/accounts/invites/${this.invite_code}">${this._renderInvText()}</a>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
get accountCreation(): boolean { return this.type == "creation"; }
|
||||||
|
get accountDeletion(): boolean { return this.type == "deletion"; }
|
||||||
|
get accountDisabled(): boolean { return this.type == "disabled"; }
|
||||||
|
get accountEnabled(): boolean { return this.type == "enabled"; }
|
||||||
|
get contactLinked(): boolean { return this.type == "contactLinked"; }
|
||||||
|
get contactUnlinked(): boolean { return this.type == "contactUnlinked"; }
|
||||||
|
get passwordChange(): boolean { return this.type == "changePassword"; }
|
||||||
|
get passwordReset(): boolean { return this.type == "resetPassword"; }
|
||||||
|
get inviteCreated(): boolean { return this.type == "createInvite"; }
|
||||||
|
get inviteDeleted(): boolean { return this.type == "deleteInvite"; }
|
||||||
|
|
||||||
|
get mentionedUsers(): string {
|
||||||
|
return (this.username + " " + this.source_username).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
get actor(): string {
|
||||||
|
let out = this.source_type + " ";
|
||||||
|
if (this.source_type == "admin" || this.source_type == "user") out += this.source_username;
|
||||||
|
return out.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
get referrer(): string {
|
||||||
|
if (this.type != "creation" || this.source_type != "user") return "";
|
||||||
|
return this.source_username.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
get type(): string { return this._act.type; }
|
get type(): string { return this._act.type; }
|
||||||
set type(v: string) {
|
set type(v: string) {
|
||||||
this._act.type = v;
|
this._act.type = v;
|
||||||
@ -195,6 +223,29 @@ export class Activity { // FIXME: Add "implements"
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get id(): string { return this._act.id; }
|
||||||
|
set id(v: string) { this._act.id = v; }
|
||||||
|
|
||||||
|
get user_id(): string { return this._act.user_id; }
|
||||||
|
set user_id(v: string) { this._act.user_id = v; }
|
||||||
|
|
||||||
|
get username(): string { return this._act.username; }
|
||||||
|
set username(v: string) { this._act.username = v; }
|
||||||
|
|
||||||
|
get source_username(): string { return this._act.source_username; }
|
||||||
|
set source_username(v: string) { this._act.source_username = v; }
|
||||||
|
|
||||||
|
get title(): string { return this._title.textContent; }
|
||||||
|
|
||||||
|
matchesSearch = (query: string): boolean => {
|
||||||
|
// console.log(this.title, "matches", query, ":", this.title.includes(query));
|
||||||
|
return (
|
||||||
|
this.title.toLowerCase().includes(query) ||
|
||||||
|
this.username.toLowerCase().includes(query) ||
|
||||||
|
this.source_username.toLowerCase().includes(query)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
constructor(act: activity) {
|
constructor(act: activity) {
|
||||||
this._card = document.createElement("div");
|
this._card = document.createElement("div");
|
||||||
|
|
||||||
@ -265,6 +316,26 @@ interface ActivitiesDTO {
|
|||||||
|
|
||||||
export class activityList {
|
export class activityList {
|
||||||
private _activityList: HTMLElement;
|
private _activityList: HTMLElement;
|
||||||
|
private _activities: { [id: string]: Activity } = {};
|
||||||
|
private _ordering: string[] = [];
|
||||||
|
private _filterArea = document.getElementById("activity-filter-area");
|
||||||
|
private _searchOptionsHeader = document.getElementById("activity-search-options-header");
|
||||||
|
private _sortingByButton = document.getElementById("activity-sort-by-field") as HTMLButtonElement;
|
||||||
|
private _notFoundPanel = document.getElementById("activity-not-found");
|
||||||
|
private _searchBox = document.getElementById("activity-search") as HTMLInputElement;
|
||||||
|
private _search: Search;
|
||||||
|
|
||||||
|
|
||||||
|
setVisibility = (activities: string[], visible: boolean) => {
|
||||||
|
this._activityList.textContent = ``;
|
||||||
|
for (let id of this._ordering) {
|
||||||
|
if (visible && activities.indexOf(id) != -1) {
|
||||||
|
this._activityList.appendChild(this._activities[id].asElement());
|
||||||
|
} else if (!visible && activities.indexOf(id) == -1) {
|
||||||
|
this._activityList.appendChild(this._activities[id].asElement());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
reload = () => {
|
reload = () => {
|
||||||
let send = {
|
let send = {
|
||||||
@ -281,17 +352,165 @@ export class activityList {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let resp = req.response as ActivitiesDTO;
|
let resp = req.response as ActivitiesDTO;
|
||||||
this._activityList.textContent = ``;
|
// FIXME: Don't destroy everything each reload!
|
||||||
|
this._activities = {};
|
||||||
for (let act of resp.activities) {
|
for (let act of resp.activities) {
|
||||||
const activity = new Activity(act);
|
this._activities[act.id] = new Activity(act);
|
||||||
this._activityList.appendChild(activity.asElement());
|
this._activityList.appendChild(this._activities[act.id].asElement());
|
||||||
|
}
|
||||||
|
this._search.items = this._activities;
|
||||||
|
// FIXME: Actually implement sorting
|
||||||
|
this._ordering = Object.keys(this._activities);
|
||||||
|
this._search.ordering = this._ordering;
|
||||||
|
|
||||||
|
if (this._search.inSearch) {
|
||||||
|
const results = this._search.search(this._searchBox.value);
|
||||||
|
this.setVisibility(results, true);
|
||||||
|
if (results.length == 0) {
|
||||||
|
this._notFoundPanel.classList.remove("unfocused");
|
||||||
|
} else {
|
||||||
|
this._notFoundPanel.classList.add("unfocused");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.setVisibility(this._ordering, true);
|
||||||
|
this._notFoundPanel.classList.add("unfocused");
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private _queries: { [field: string]: QueryType } = {
|
||||||
|
"id": {
|
||||||
|
name: window.lang.strings("activityID"),
|
||||||
|
getter: "id",
|
||||||
|
bool: false,
|
||||||
|
string: true,
|
||||||
|
date: false
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
name: window.lang.strings("title"),
|
||||||
|
getter: "title",
|
||||||
|
bool: false,
|
||||||
|
string: true,
|
||||||
|
date: false
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
name: window.lang.strings("usersMentioned"),
|
||||||
|
getter: "mentionedUsers",
|
||||||
|
bool: false,
|
||||||
|
string: true,
|
||||||
|
date: false
|
||||||
|
},
|
||||||
|
"actor": {
|
||||||
|
name: window.lang.strings("actor"),
|
||||||
|
description: window.lang.strings("actorDescription"),
|
||||||
|
getter: "actor",
|
||||||
|
bool: false,
|
||||||
|
string: true,
|
||||||
|
date: false
|
||||||
|
},
|
||||||
|
"referrer": {
|
||||||
|
name: window.lang.strings("referrer"),
|
||||||
|
getter: "referrer",
|
||||||
|
bool: true,
|
||||||
|
string: true,
|
||||||
|
date: false
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
name: window.lang.strings("date"),
|
||||||
|
getter: "date",
|
||||||
|
bool: false,
|
||||||
|
string: false,
|
||||||
|
date: true
|
||||||
|
},
|
||||||
|
"account-creation": {
|
||||||
|
name: window.lang.strings("accountCreationFilter"),
|
||||||
|
getter: "accountCreation",
|
||||||
|
bool: true,
|
||||||
|
string: false,
|
||||||
|
date: false
|
||||||
|
},
|
||||||
|
"account-deletion": {
|
||||||
|
name: window.lang.strings("accountDeletionFilter"),
|
||||||
|
getter: "accountDeletion",
|
||||||
|
bool: true,
|
||||||
|
string: false,
|
||||||
|
date: false
|
||||||
|
},
|
||||||
|
"account-disabled": {
|
||||||
|
name: window.lang.strings("accountDisabledFilter"),
|
||||||
|
getter: "accountDisabled",
|
||||||
|
bool: true,
|
||||||
|
string: false,
|
||||||
|
date: false
|
||||||
|
},
|
||||||
|
"account-enabled": {
|
||||||
|
name: window.lang.strings("accountEnabledFilter"),
|
||||||
|
getter: "accountEnabled",
|
||||||
|
bool: true,
|
||||||
|
string: false,
|
||||||
|
date: false
|
||||||
|
},
|
||||||
|
"contact-linked": {
|
||||||
|
name: window.lang.strings("contactLinkedFilter"),
|
||||||
|
getter: "contactLinked",
|
||||||
|
bool: true,
|
||||||
|
string: false,
|
||||||
|
date: false
|
||||||
|
},
|
||||||
|
"contact-unlinked": {
|
||||||
|
name: window.lang.strings("contactUnlinkedFilter"),
|
||||||
|
getter: "contactUnlinked",
|
||||||
|
bool: true,
|
||||||
|
string: false,
|
||||||
|
date: false
|
||||||
|
},
|
||||||
|
"password-change": {
|
||||||
|
name: window.lang.strings("passwordChangeFilter"),
|
||||||
|
getter: "passwordChange",
|
||||||
|
bool: true,
|
||||||
|
string: false,
|
||||||
|
date: false
|
||||||
|
},
|
||||||
|
"password-reset": {
|
||||||
|
name: window.lang.strings("passwordResetFilter"),
|
||||||
|
getter: "passwordReset",
|
||||||
|
bool: true,
|
||||||
|
string: false,
|
||||||
|
date: false
|
||||||
|
},
|
||||||
|
"invite-created": {
|
||||||
|
name: window.lang.strings("inviteCreatedFilter"),
|
||||||
|
getter: "inviteCreated",
|
||||||
|
bool: true,
|
||||||
|
string: false,
|
||||||
|
date: false
|
||||||
|
},
|
||||||
|
"invite-deleted": {
|
||||||
|
name: window.lang.strings("inviteDeletedFilter"),
|
||||||
|
getter: "inviteDeleted",
|
||||||
|
bool: true,
|
||||||
|
string: false,
|
||||||
|
date: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this._activityList = document.getElementById("activity-card-list");
|
this._activityList = document.getElementById("activity-card-list");
|
||||||
document.addEventListener("activity-reload", this.reload);
|
document.addEventListener("activity-reload", this.reload);
|
||||||
|
|
||||||
|
let conf: SearchConfiguration = {
|
||||||
|
filterArea: this._filterArea,
|
||||||
|
sortingByButton: this._sortingByButton,
|
||||||
|
searchOptionsHeader: this._searchOptionsHeader,
|
||||||
|
notFoundPanel: this._notFoundPanel,
|
||||||
|
search: this._searchBox,
|
||||||
|
clearSearchButtonSelector: ".activity-search-clear",
|
||||||
|
queries: this._queries,
|
||||||
|
setVisibility: this.setVisibility,
|
||||||
|
filterList: document.getElementById("activity-filter-list"),
|
||||||
|
onSearchCallback: () => {}
|
||||||
|
}
|
||||||
|
this._search = new Search(conf);
|
||||||
|
this._search.generateFilterList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ const dateParser = require("any-date-parser");
|
|||||||
|
|
||||||
export interface QueryType {
|
export interface QueryType {
|
||||||
name: string;
|
name: string;
|
||||||
|
description?: string;
|
||||||
getter: string;
|
getter: string;
|
||||||
bool: boolean;
|
bool: boolean;
|
||||||
string: boolean;
|
string: boolean;
|
||||||
@ -15,6 +16,7 @@ export interface SearchConfiguration {
|
|||||||
sortingByButton: HTMLButtonElement;
|
sortingByButton: HTMLButtonElement;
|
||||||
searchOptionsHeader: HTMLElement;
|
searchOptionsHeader: HTMLElement;
|
||||||
notFoundPanel: HTMLElement;
|
notFoundPanel: HTMLElement;
|
||||||
|
filterList: HTMLElement;
|
||||||
clearSearchButtonSelector: string;
|
clearSearchButtonSelector: string;
|
||||||
search: HTMLInputElement;
|
search: HTMLInputElement;
|
||||||
queries: { [field: string]: QueryType };
|
queries: { [field: string]: QueryType };
|
||||||
@ -158,7 +160,7 @@ export class Search {
|
|||||||
let cachedResult = [...result];
|
let cachedResult = [...result];
|
||||||
for (let id of cachedResult) {
|
for (let id of cachedResult) {
|
||||||
const u = this._items[id];
|
const u = this._items[id];
|
||||||
const value = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(u), queryFormat.getter).get.call(u);
|
const value = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(u), queryFormat.getter).get.call(u).toLowerCase();
|
||||||
if (!(value.includes(split[1]))) {
|
if (!(value.includes(split[1]))) {
|
||||||
result.splice(result.indexOf(id), 1);
|
result.splice(result.indexOf(id), 1);
|
||||||
}
|
}
|
||||||
@ -283,6 +285,90 @@ export class Search {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fillInFilter = (name: string, value: string, offset?: number) => {
|
||||||
|
this._c.search.value = name + ":" + value + " " + this._c.search.value;
|
||||||
|
this._c.search.focus();
|
||||||
|
let newPos = name.length + 1 + value.length;
|
||||||
|
if (typeof offset !== 'undefined')
|
||||||
|
newPos += offset;
|
||||||
|
this._c.search.setSelectionRange(newPos, newPos);
|
||||||
|
this._c.search.oninput(null as any);
|
||||||
|
};
|
||||||
|
|
||||||
|
generateFilterList = () => {
|
||||||
|
// Generate filter buttons
|
||||||
|
for (let queryName of Object.keys(this._c.queries)) {
|
||||||
|
const query = this._c.queries[queryName];
|
||||||
|
if ("show" in query && !query.show) continue;
|
||||||
|
if ("dependsOnElement" in query && query.dependsOnElement) {
|
||||||
|
const el = document.querySelector(query.dependsOnElement);
|
||||||
|
if (el === null) continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = document.createElement("span") as HTMLSpanElement;
|
||||||
|
container.classList.add("button", "button-xl", "~neutral", "@low", "mb-1", "mr-2");
|
||||||
|
container.innerHTML = `
|
||||||
|
<div class="flex flex-col mr-2">
|
||||||
|
<span>${query.name}</span>
|
||||||
|
<span class="support">${query.description || ""}</span>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
if (query.bool) {
|
||||||
|
const pos = document.createElement("button") as HTMLButtonElement;
|
||||||
|
pos.type = "button";
|
||||||
|
pos.ariaLabel = `Filter by "${query.name}": True`;
|
||||||
|
pos.classList.add("button", "~positive", "ml-2");
|
||||||
|
pos.innerHTML = `<i class="ri-checkbox-circle-fill"></i>`;
|
||||||
|
pos.addEventListener("click", () => this.fillInFilter(queryName, "true"));
|
||||||
|
const neg = document.createElement("button") as HTMLButtonElement;
|
||||||
|
neg.type = "button";
|
||||||
|
neg.ariaLabel = `Filter by "${query.name}": False`;
|
||||||
|
neg.classList.add("button", "~critical", "ml-2");
|
||||||
|
neg.innerHTML = `<i class="ri-close-circle-fill"></i>`;
|
||||||
|
neg.addEventListener("click", () => this.fillInFilter(queryName, "false"));
|
||||||
|
|
||||||
|
container.appendChild(pos);
|
||||||
|
container.appendChild(neg);
|
||||||
|
}
|
||||||
|
if (query.string) {
|
||||||
|
const button = document.createElement("button") as HTMLButtonElement;
|
||||||
|
button.type = "button";
|
||||||
|
button.classList.add("button", "~urge", "ml-2");
|
||||||
|
button.innerHTML = `<i class="ri-equal-line mr-2"></i>${window.lang.strings("matchText")}`;
|
||||||
|
|
||||||
|
// Position cursor between quotes
|
||||||
|
button.addEventListener("click", () => this.fillInFilter(queryName, `""`, -1));
|
||||||
|
|
||||||
|
container.appendChild(button);
|
||||||
|
}
|
||||||
|
if (query.date) {
|
||||||
|
const onDate = document.createElement("button") as HTMLButtonElement;
|
||||||
|
onDate.type = "button";
|
||||||
|
onDate.classList.add("button", "~urge", "ml-2");
|
||||||
|
onDate.innerHTML = `<i class="ri-calendar-check-line mr-2"></i>On Date`;
|
||||||
|
onDate.addEventListener("click", () => this.fillInFilter(queryName, `"="`, -1));
|
||||||
|
|
||||||
|
const beforeDate = document.createElement("button") as HTMLButtonElement;
|
||||||
|
beforeDate.type = "button";
|
||||||
|
beforeDate.classList.add("button", "~urge", "ml-2");
|
||||||
|
beforeDate.innerHTML = `<i class="ri-calendar-check-line mr-2"></i>Before Date`;
|
||||||
|
beforeDate.addEventListener("click", () => this.fillInFilter(queryName, `"<"`, -1));
|
||||||
|
|
||||||
|
const afterDate = document.createElement("button") as HTMLButtonElement;
|
||||||
|
afterDate.type = "button";
|
||||||
|
afterDate.classList.add("button", "~urge", "ml-2");
|
||||||
|
afterDate.innerHTML = `<i class="ri-calendar-check-line mr-2"></i>After Date`;
|
||||||
|
afterDate.addEventListener("click", () => this.fillInFilter(queryName, `">"`, -1));
|
||||||
|
|
||||||
|
container.appendChild(onDate);
|
||||||
|
container.appendChild(beforeDate);
|
||||||
|
container.appendChild(afterDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
this._c.filterList.appendChild(container);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
constructor(c: SearchConfiguration) {
|
constructor(c: SearchConfiguration) {
|
||||||
this._c = c;
|
this._c = c;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user