impr(quote search modal): add custom length filter (@Leonabcd123) (#7109)

### Description

Added a new filter option in the quotes search page that allows the user
to search for quotes by a minimum and a maximum length.

Implementing #1323

---------

Co-authored-by: Jack <jack@monkeytype.com>
This commit is contained in:
Leonabcd123 2025-12-03 17:42:18 +02:00 committed by GitHub
parent 71c0f43d96
commit 54e660da6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 120 additions and 3 deletions

View file

@ -0,0 +1,51 @@
import { SimpleModal } from "../utils/simple-modal";
export let minFilterLength: number = 0;
export let maxFilterLength: number = 0;
export let removeCustom: boolean = false;
export function setRemoveCustom(value: boolean): void {
removeCustom = value;
}
function refresh(): void {
const refreshEvent = new CustomEvent("refresh");
document.dispatchEvent(refreshEvent);
}
export const quoteFilterModal = new SimpleModal({
id: "quoteFilter",
title: "Enter minimum and maximum values",
inputs: [
{
placeholder: "1",
type: "number",
},
{
placeholder: "100",
type: "number",
},
],
buttonText: "save",
execFn: async (_thisPopup, min, max) => {
const minNum = parseInt(min, 10);
const maxNum = parseInt(max, 10);
if (isNaN(minNum) || isNaN(maxNum)) {
return {
status: 0,
message: "Invalid min/max values",
};
}
minFilterLength = minNum;
maxFilterLength = maxNum;
refresh();
let message: string = "saved custom filter";
return { status: 1, message };
},
afterClickAway: () => {
setRemoveCustom(true);
refresh();
},
});

View file

@ -4,6 +4,7 @@ import * as ManualRestart from "../test/manual-restart-tracker";
import * as Notifications from "../elements/notifications";
import * as QuoteSubmitPopup from "./quote-submit";
import * as QuoteApprovePopup from "./quote-approve";
import * as QuoteFilterPopup from "./quote-filter";
import * as QuoteReportModal from "./quote-report";
import {
buildSearchService,
@ -26,6 +27,7 @@ const searchServiceCache: Record<string, SearchService<Quote>> = {};
const pageSize = 100;
let currentPageNumber = 1;
let usingCustomLength = true;
function getSearchService<T>(
language: string,
@ -45,16 +47,65 @@ function getSearchService<T>(
function applyQuoteLengthFilter(quotes: Quote[]): Quote[] {
if (!modal.isOpen()) return [];
const quoteLengthFilterValue = $(
"#quoteSearchModal .quoteLengthFilter",
).val() as string[];
const quoteLengthDropdown = $("#quoteSearchModal .quoteLengthFilter");
const quoteLengthFilterValue = quoteLengthDropdown.val() as string[];
if (quoteLengthFilterValue.length === 0) {
usingCustomLength = true;
return quotes;
}
const quoteLengthFilter = new Set(
quoteLengthFilterValue.map((filterValue) => parseInt(filterValue, 10)),
);
const customFilterIndex = quoteLengthFilterValue.indexOf("4");
if (customFilterIndex !== -1) {
if (QuoteFilterPopup.removeCustom) {
QuoteFilterPopup.setRemoveCustom(false);
const selectElement = quoteLengthDropdown.get(0) as
| HTMLSelectElement
| null
| undefined;
if (!selectElement) {
return quotes;
}
//@ts-expect-error SlimSelect adds slim to the element
const ss = selectElement.slim as SlimSelect | undefined;
if (ss !== undefined) {
const currentSelected = ss.getSelected();
// remove custom selection
const customIndex = currentSelected.indexOf("4");
if (customIndex > -1) {
currentSelected.splice(customIndex, 1);
}
ss.setSelected(currentSelected);
}
} else {
if (usingCustomLength) {
QuoteFilterPopup.quoteFilterModal.show(undefined, {});
usingCustomLength = false;
} else {
const filteredQuotes = quotes.filter(
(quote) =>
(quote.length >= QuoteFilterPopup.minFilterLength &&
quote.length <= QuoteFilterPopup.maxFilterLength) ||
quoteLengthFilter.has(quote.group),
);
return filteredQuotes;
}
}
} else {
usingCustomLength = true;
}
const filteredQuotes = quotes.filter((quote) =>
quoteLengthFilter.has(quote.group),
);
@ -281,6 +332,10 @@ export async function show(showOptions?: ShowOptions): Promise<void> {
text: "thicc",
value: "3",
},
{
text: "custom",
value: "4",
},
],
});
},
@ -437,6 +492,13 @@ async function setup(modalEl: HTMLElement): Promise<void> {
currentPageNumber--;
void updateResults(searchText);
});
document?.addEventListener("refresh", () => {
const searchText = (
document.getElementById("searchBox") as HTMLInputElement
).value;
void updateResults(searchText);
});
}
async function cleanup(): Promise<void> {

View file

@ -111,6 +111,7 @@ type SimpleModalOptions = {
onlineOnly?: boolean;
hideCallsExec?: boolean;
showLabels?: boolean;
afterClickAway?: () => void;
};
export class SimpleModal {
@ -131,6 +132,7 @@ export class SimpleModal {
onlineOnly: boolean;
hideCallsExec: boolean;
showLabels: boolean;
afterClickAway: (() => void) | undefined;
constructor(options: SimpleModalOptions) {
this.parameters = [];
this.id = options.id;
@ -149,6 +151,7 @@ export class SimpleModal {
this.onlineOnly = options.onlineOnly ?? false;
this.hideCallsExec = options.hideCallsExec ?? false;
this.showLabels = options.showLabels ?? false;
this.afterClickAway = options.afterClickAway;
}
reset(): void {
this.element.innerHTML = `
@ -480,6 +483,7 @@ const modal = new AnimatedModal({
hide();
},
customWrapperClickHandler: (e): void => {
activePopup?.afterClickAway?.();
hide();
},
});