Skip to content

Commit 2886c72

Browse files
Leonabcd123Miodec
andauthored
impr(words-filter): add 'exact match only' checkbox (@Leonabcd123) (monkeytypegame#7126)
### Description Added a checkbox in the words filter modal that allows the user to make all characters not in the include list be excluded. Implements monkeytypegame#5970 --------- Co-authored-by: Miodec <[email protected]>
1 parent f6df272 commit 2886c72

File tree

3 files changed

+67
-44
lines changed

3 files changed

+67
-44
lines changed

frontend/src/html/popups.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -742,6 +742,10 @@
742742
autocomplete="off"
743743
title="include"
744744
/>
745+
<label class="checkbox">
746+
<input id="exactMatchOnly" type="checkbox" />
747+
Exact match only
748+
</label>
745749
</div>
746750
<div class="group">
747751
<div class="title">exclude</div>

frontend/src/styles/inputs.scss

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,10 @@ label.checkboxWithSub {
102102
}
103103
}
104104

105+
#wordFilterModal #exactMatchOnly {
106+
width: 1.25em;
107+
}
108+
105109
input[type="checkbox"] {
106110
appearance: none;
107111
height: 1.25em;

frontend/src/ts/modals/word-filter.ts

Lines changed: 59 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,17 @@ import { LayoutObject } from "@monkeytype/schemas/layouts";
1616
type FilterPreset = {
1717
display: string;
1818
getIncludeString: (layout: LayoutObject) => string[][];
19-
getExcludeString: (layout: LayoutObject) => string[][];
20-
};
19+
} & (
20+
| {
21+
exactMatch: true;
22+
}
23+
| {
24+
exactMatch?: false;
25+
getExcludeString?: (layout: LayoutObject) => string[][];
26+
}
27+
);
28+
29+
const exactMatchCheckbox = $("#wordFilterModal #exactMatchOnly");
2130

2231
const presets: Record<string, FilterPreset> = {
2332
homeKeys: {
@@ -27,13 +36,7 @@ const presets: Record<string, FilterPreset> = {
2736
const homeKeysRight = layout.keys.row3.slice(6, 10);
2837
return [...homeKeysLeft, ...homeKeysRight];
2938
},
30-
getExcludeString: (layout) => {
31-
const topRow = layout.keys.row2;
32-
const bottomRow = layout.keys.row4;
33-
const homeRowRight = layout.keys.row3.slice(10);
34-
const homeRowMiddle = layout.keys.row3.slice(4, 6);
35-
return [...topRow, ...homeRowMiddle, ...homeRowRight, ...bottomRow];
36-
},
39+
exactMatch: true,
3740
},
3841
leftHand: {
3942
display: "left hand",
@@ -43,12 +46,7 @@ const presets: Record<string, FilterPreset> = {
4346
const bottomRowInclude = layout.keys.row4.slice(0, 5);
4447
return [...topRowInclude, ...homeRowInclude, ...bottomRowInclude];
4548
},
46-
getExcludeString: (layout) => {
47-
const topRowExclude = layout.keys.row2.slice(5);
48-
const homeRowExclude = layout.keys.row3.slice(5);
49-
const bottomRowExclude = layout.keys.row4.slice(5);
50-
return [...topRowExclude, ...homeRowExclude, ...bottomRowExclude];
51-
},
49+
exactMatch: true,
5250
},
5351
rightHand: {
5452
display: "right hand",
@@ -58,45 +56,28 @@ const presets: Record<string, FilterPreset> = {
5856
const bottomRowInclude = layout.keys.row4.slice(4);
5957
return [...topRowInclude, ...homeRowInclude, ...bottomRowInclude];
6058
},
61-
getExcludeString: (layout) => {
62-
const topRowExclude = layout.keys.row2.slice(0, 5);
63-
const homeRowExclude = layout.keys.row3.slice(0, 5);
64-
const bottomRowExclude = layout.keys.row4.slice(0, 4);
65-
return [...topRowExclude, ...homeRowExclude, ...bottomRowExclude];
66-
},
59+
exactMatch: true,
6760
},
6861
homeRow: {
6962
display: "home row",
7063
getIncludeString: (layout) => {
7164
return layout.keys.row3;
7265
},
73-
getExcludeString: (layout) => {
74-
const topRowExclude = layout.keys.row2;
75-
const bottomRowExclude = layout.keys.row4;
76-
return [...topRowExclude, ...bottomRowExclude];
77-
},
66+
exactMatch: true,
7867
},
7968
topRow: {
8069
display: "top row",
8170
getIncludeString: (layout) => {
8271
return layout.keys.row2;
8372
},
84-
getExcludeString: (layout) => {
85-
const homeRowExclude = layout.keys.row3;
86-
const bottomRowExclude = layout.keys.row4;
87-
return [...homeRowExclude, ...bottomRowExclude];
88-
},
73+
exactMatch: true,
8974
},
9075
bottomRow: {
9176
display: "bottom row",
9277
getIncludeString: (layout) => {
9378
return layout.keys.row4;
9479
},
95-
getExcludeString: (layout) => {
96-
const topRowExclude = layout.keys.row2;
97-
const homeRowExclude = layout.keys.row3;
98-
return [...topRowExclude, ...homeRowExclude];
99-
},
80+
exactMatch: true,
10081
},
10182
};
10283

@@ -165,10 +146,18 @@ function hide(hideOptions?: HideOptions<OutgoingData>): void {
165146
}
166147

167148
async function filter(language: Language): Promise<string[]> {
149+
const exactMatchOnly = exactMatchCheckbox.is(":checked");
168150
let filterin = $("#wordFilterModal .wordIncludeInput").val() as string;
169151
filterin = Misc.escapeRegExp(filterin?.trim());
170152
filterin = filterin.replace(/\s+/gi, "|");
171-
const regincl = new RegExp(filterin, "i");
153+
let regincl;
154+
155+
if (exactMatchOnly) {
156+
regincl = new RegExp("^[" + filterin + "]+$", "i");
157+
} else {
158+
regincl = new RegExp(filterin, "i");
159+
}
160+
172161
let filterout = $("#wordFilterModal .wordExcludeInput").val() as string;
173162
filterout = Misc.escapeRegExp(filterout.trim());
174163
filterout = filterout.replace(/\s+/gi, "|");
@@ -202,7 +191,7 @@ async function filter(language: Language): Promise<string[]> {
202191
}
203192
for (const word of languageWordList.words) {
204193
const test1 = regincl.test(word);
205-
const test2 = regexcl.test(word);
194+
const test2 = exactMatchOnly ? false : regexcl.test(word);
206195
if (
207196
((test1 && !test2) || (test1 && filterout === "")) &&
208197
word.length <= maxLength &&
@@ -236,6 +225,19 @@ async function apply(set: boolean): Promise<void> {
236225
});
237226
}
238227

228+
function setExactMatchInput(disable: boolean): void {
229+
const wordExcludeInputEl = $("#wordFilterModal #wordExcludeInput");
230+
231+
if (disable) {
232+
$("#wordFilterModal #wordExcludeInput").val("");
233+
wordExcludeInputEl.attr("disabled", "disabled");
234+
} else {
235+
wordExcludeInputEl.removeAttr("disabled");
236+
}
237+
238+
exactMatchCheckbox.prop("checked", disable);
239+
}
240+
239241
function disableButtons(): void {
240242
for (const button of modal.getModal().querySelectorAll("button")) {
241243
button.setAttribute("disabled", "true");
@@ -270,13 +272,26 @@ async function setup(): Promise<void> {
270272
.map((x) => x[0])
271273
.join(" "),
272274
);
273-
$("#wordExcludeInput").val(
274-
presetToApply
275-
.getExcludeString(layout)
276-
.map((x) => x[0])
277-
.join(" "),
278-
);
275+
276+
if (presetToApply.exactMatch === true) {
277+
setExactMatchInput(true);
278+
} else {
279+
setExactMatchInput(false);
280+
if (presetToApply.getExcludeString !== undefined) {
281+
$("#wordExcludeInput").val(
282+
presetToApply
283+
.getExcludeString(layout)
284+
.map((x) => x[0])
285+
.join(" "),
286+
);
287+
}
288+
}
279289
});
290+
291+
exactMatchCheckbox.on("change", () => {
292+
setExactMatchInput(exactMatchCheckbox.is(":checked"));
293+
});
294+
280295
$("#wordFilterModal button.addButton").on("click", () => {
281296
$("#wordFilterModal .loadingIndicator").removeClass("hidden");
282297
disableButtons();

0 commit comments

Comments
 (0)