mirror of
https://github.com/monkeytypegame/monkeytype.git
synced 2024-11-10 17:04:49 +08:00
Add quote length filter (#2705) Bruception
* Add quote length filter * Clear filters when closing search * Add select2 style selectors * updated placeholders * fixed styling Co-authored-by: Miodec <bartnikjack@gmail.com>
This commit is contained in:
parent
ab27950bcc
commit
eb79ffbc00
7 changed files with 216 additions and 61 deletions
|
@ -63,6 +63,7 @@ class QuotesController {
|
|||
length: quote.length,
|
||||
id: quote.id,
|
||||
language: language,
|
||||
group: 0,
|
||||
};
|
||||
|
||||
this.quoteCollection.quotes.push(monkeyTypeQuote);
|
||||
|
|
|
@ -54,6 +54,70 @@ function highlightMatches(text: string, matchedText: string[]): string {
|
|||
return normalizedWords.join("");
|
||||
}
|
||||
|
||||
function applyQuoteLengthFilter(
|
||||
quotes: MonkeyTypes.Quote[]
|
||||
): MonkeyTypes.Quote[] {
|
||||
const quoteLengthFilterValue = $(
|
||||
"#quoteSearchPopup .quoteLengthFilter"
|
||||
).val() as string[];
|
||||
if (quoteLengthFilterValue.length === 0) {
|
||||
return quotes;
|
||||
}
|
||||
|
||||
const quoteLengthFilter = quoteLengthFilterValue.map((filterValue) =>
|
||||
parseInt(filterValue, 10)
|
||||
);
|
||||
const filteredQuotes = quotes.filter((quote) =>
|
||||
quoteLengthFilter.includes(quote.group)
|
||||
);
|
||||
|
||||
return filteredQuotes;
|
||||
}
|
||||
|
||||
function buildQuoteSearchResult(
|
||||
quote: MonkeyTypes.Quote,
|
||||
matchedSearchTerms: string[]
|
||||
): string {
|
||||
let lengthDesc;
|
||||
if (quote.length < 101) {
|
||||
lengthDesc = "short";
|
||||
} else if (quote.length < 301) {
|
||||
lengthDesc = "medium";
|
||||
} else if (quote.length < 601) {
|
||||
lengthDesc = "long";
|
||||
} else {
|
||||
lengthDesc = "thicc";
|
||||
}
|
||||
|
||||
const isNotAuthed = !firebase.auth().currentUser;
|
||||
|
||||
return `
|
||||
<div class="searchResult" id="${quote.id}">
|
||||
<div class="text">
|
||||
${highlightMatches(quote.text, matchedSearchTerms)}
|
||||
</div>
|
||||
<div class="id">
|
||||
<div class="sub">id</div>
|
||||
<span class="quote-id">
|
||||
${highlightMatches(quote.id.toString(), matchedSearchTerms)}
|
||||
</span>
|
||||
</div>
|
||||
<div class="length">
|
||||
<div class="sub">length</div>
|
||||
${lengthDesc}
|
||||
</div>
|
||||
<div class="source">
|
||||
<div class="sub">source</div>
|
||||
${highlightMatches(quote.source, matchedSearchTerms)}</div>
|
||||
<div class="icon-button report ${
|
||||
isNotAuthed && "hidden"
|
||||
}" aria-label="Report quote" data-balloon-pos="left">
|
||||
<i class="fas fa-flag report"></i>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
async function updateResults(searchText: string): Promise<void> {
|
||||
const { quotes } = await QuotesController.getQuotes(Config.language);
|
||||
|
||||
|
@ -67,58 +131,25 @@ async function updateResults(searchText: string): Promise<void> {
|
|||
const { results: matches, matchedQueryTerms } =
|
||||
quoteSearchService.query(searchText);
|
||||
|
||||
$("#quoteSearchResults").remove();
|
||||
$("#quoteSearchPopup").append(
|
||||
'<div class="quoteSearchResults" id="quoteSearchResults"></div>'
|
||||
const quotesToShow = applyQuoteLengthFilter(
|
||||
searchText === "" ? quotes : matches
|
||||
);
|
||||
|
||||
const resultsList = $("#quoteSearchResults");
|
||||
const isNotAuthed = !firebase.auth().currentUser;
|
||||
|
||||
const quotesToShow = searchText === "" ? quotes : matches;
|
||||
resultsList.empty();
|
||||
|
||||
quotesToShow.slice(0, 100).forEach((quote) => {
|
||||
let lengthDesc;
|
||||
if (quote.length < 101) {
|
||||
lengthDesc = "short";
|
||||
} else if (quote.length < 301) {
|
||||
lengthDesc = "medium";
|
||||
} else if (quote.length < 601) {
|
||||
lengthDesc = "long";
|
||||
} else {
|
||||
lengthDesc = "thicc";
|
||||
}
|
||||
resultsList.append(`
|
||||
<div class="searchResult" id="${quote.id}">
|
||||
<div class="text">${highlightMatches(
|
||||
quote.text,
|
||||
matchedQueryTerms
|
||||
)}</div>
|
||||
<div class="id"><div class="sub">id</div><span class="quote-id">${highlightMatches(
|
||||
quote.id.toString(),
|
||||
matchedQueryTerms
|
||||
)}</span></div>
|
||||
<div class="length"><div class="sub">length</div>${lengthDesc}</div>
|
||||
<div class="source"><div class="sub">source</div>${highlightMatches(
|
||||
quote.source,
|
||||
matchedQueryTerms
|
||||
)}</div>
|
||||
<div class="icon-button report ${
|
||||
isNotAuthed && "hidden"
|
||||
}" aria-label="Report quote" data-balloon-pos="left">
|
||||
<i class="fas fa-flag report"></i>
|
||||
</div>
|
||||
</div>
|
||||
`);
|
||||
const quoteSearchResult = buildQuoteSearchResult(quote, matchedQueryTerms);
|
||||
resultsList.append(quoteSearchResult);
|
||||
});
|
||||
if (quotesToShow.length > 100) {
|
||||
$("#extraResults").html(
|
||||
quotesToShow.length +
|
||||
" results <span style='opacity: 0.5'>(only showing 100)</span>"
|
||||
);
|
||||
} else {
|
||||
$("#extraResults").html(quotesToShow.length + " results");
|
||||
}
|
||||
|
||||
const resultsExceededText =
|
||||
quotesToShow.length > 100
|
||||
? "<span style='opacity: 0.5'>(only showing 100)</span>"
|
||||
: "";
|
||||
$("#extraResults").html(
|
||||
`${quotesToShow.length} results ${resultsExceededText}`
|
||||
);
|
||||
}
|
||||
|
||||
export async function show(clearText = true): Promise<void> {
|
||||
|
@ -141,6 +172,35 @@ export async function show(clearText = true): Promise<void> {
|
|||
$("#quoteSearchPopup #goToApproveQuotes").addClass("hidden");
|
||||
}
|
||||
|
||||
$("#quoteSearchPopup .quoteLengthFilter").select2({
|
||||
placeholder: "Filter by length",
|
||||
maximumSelectionLength: Infinity,
|
||||
multiple: true,
|
||||
width: "100%",
|
||||
data: [
|
||||
{
|
||||
id: 0,
|
||||
text: "short",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
text: "medium",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
text: "long",
|
||||
selected: false,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
text: "thicc",
|
||||
selected: false,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
$("#quoteSearchPopupWrapper")
|
||||
.stop(true, true)
|
||||
.css("opacity", 0)
|
||||
|
@ -166,8 +226,11 @@ export function hide(noAnim = false, focusWords = true): void {
|
|||
noAnim ? 0 : 100,
|
||||
() => {
|
||||
$("#quoteSearchPopupWrapper").addClass("hidden");
|
||||
|
||||
if (focusWords) {
|
||||
TestUI.focusWords();
|
||||
$("#quoteSearchPopup .quoteLengthFilter").val([]);
|
||||
$("#quoteSearchPopup .quoteLengthFilter").trigger("change");
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -194,16 +257,19 @@ export function apply(val: number): boolean {
|
|||
return ret;
|
||||
}
|
||||
|
||||
const debouncedSearch = debounce(updateResults);
|
||||
const searchForQuotes = debounce((): void => {
|
||||
const searchText = (<HTMLInputElement>document.getElementById("searchBox"))
|
||||
.value;
|
||||
updateResults(searchText);
|
||||
});
|
||||
|
||||
$("#quoteSearchPopup .searchBox").on("keyup", (e) => {
|
||||
if (e.code === "Escape") return;
|
||||
|
||||
const searchText = (<HTMLInputElement>document.getElementById("searchBox"))
|
||||
.value;
|
||||
debouncedSearch(searchText);
|
||||
searchForQuotes();
|
||||
});
|
||||
|
||||
$("#quoteSearchPopup .quoteLengthFilter").on("change", searchForQuotes);
|
||||
|
||||
$("#quoteSearchPopupWrapper").on("click", (e) => {
|
||||
if ($(e.target).attr("id") === "quoteSearchPopupWrapper") {
|
||||
hide();
|
||||
|
@ -237,7 +303,6 @@ $(document).on("click", "#quoteSearchPopup .report", async (e) => {
|
|||
$(document).on("click", "#top .config .quoteLength .text-button", (e) => {
|
||||
const len = $(e.currentTarget).attr("quoteLength") ?? (0 as number);
|
||||
if (len == -2) {
|
||||
// UpdateConfig.setQuoteLength(-2, false, e.shiftKey);
|
||||
show();
|
||||
}
|
||||
});
|
||||
|
|
2
frontend/src/scripts/types/types.d.ts
vendored
2
frontend/src/scripts/types/types.d.ts
vendored
|
@ -591,7 +591,7 @@ declare namespace MonkeyTypes {
|
|||
source: string;
|
||||
length: number;
|
||||
id: number;
|
||||
group?: number;
|
||||
group: number;
|
||||
language: string;
|
||||
textSplit?: string[];
|
||||
}
|
||||
|
|
|
@ -182,3 +182,73 @@ input[type="number"] {
|
|||
.select2-container--default .select2-results__group {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--multiple {
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
border-radius: var(--roundness);
|
||||
color: var(--text-color);
|
||||
outline: none;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.select2-selection__choice__display {
|
||||
color: var(--bg-color);
|
||||
}
|
||||
|
||||
.select2-selection__choice__remove {
|
||||
color: var(--main-color) !important;
|
||||
}
|
||||
|
||||
.select2-container .select2-search--inline .select2-search__field {
|
||||
margin: 0;
|
||||
border-radius: 0;
|
||||
font-family: var(--font);
|
||||
line-height: 1rem;
|
||||
}
|
||||
|
||||
.select2-container--default.select2-container--focus
|
||||
.select2-selection--multiple {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.select2-container--default .select2-selection--multiple {
|
||||
border: none;
|
||||
padding: 0.5rem;
|
||||
font-family: var(--font);
|
||||
}
|
||||
|
||||
.select2-container--default
|
||||
.select2-selection--multiple
|
||||
.select2-selection__choice {
|
||||
background-color: var(--main-color);
|
||||
border: none;
|
||||
border-radius: calc(var(--roundness) / 2);
|
||||
box-sizing: border-box;
|
||||
display: inline-block;
|
||||
margin-left: 0;
|
||||
margin-top: 0;
|
||||
margin-right: 0.5rem;
|
||||
padding: 0;
|
||||
padding-left: 1.25rem;
|
||||
position: relative;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
vertical-align: bottom;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.select2-container--default
|
||||
.select2-selection--multiple
|
||||
.select2-selection__choice__remove {
|
||||
line-height: 1.5rem;
|
||||
border-right: 1px solid var(--bg-color);
|
||||
color: var(--bg-color) !important;
|
||||
transition: 0.125s;
|
||||
}
|
||||
|
||||
.select2-container--default
|
||||
.select2-selection--multiple
|
||||
.select2-selection__choice__remove:hover {
|
||||
background-color: var(--text-color);
|
||||
}
|
||||
|
|
|
@ -446,6 +446,15 @@
|
|||
}
|
||||
}
|
||||
|
||||
#quoteSearchControlsWrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1.5fr 1fr;
|
||||
gap: 1rem;
|
||||
#searchBox {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#extraResults {
|
||||
text-align: center;
|
||||
color: var(--sub-color);
|
||||
|
@ -455,6 +464,7 @@
|
|||
gap: 0.5rem;
|
||||
height: auto;
|
||||
overflow-y: scroll;
|
||||
align-content: baseline;
|
||||
|
||||
.searchResult {
|
||||
display: grid;
|
||||
|
|
|
@ -32,13 +32,18 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
#quoteSearchPopupWrapper #quoteSearchPopup #quoteSearchControlsWrapper {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 1050px) {
|
||||
.pageSettings .section.fullWidth .buttons {
|
||||
grid-template-columns: 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
#quoteSearchPopupWrapper #quoteSearchPopup #quoteSearchControlsWrapper {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
#result .morestats {
|
||||
gap: 1rem;
|
||||
grid-template-rows: 1fr 1fr;
|
||||
|
|
|
@ -517,13 +517,17 @@
|
|||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
id="searchBox"
|
||||
class="searchBox"
|
||||
type="text"
|
||||
maxlength="200"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<div id="quoteSearchControlsWrapper">
|
||||
<input
|
||||
id="searchBox"
|
||||
class="searchBox"
|
||||
type="text"
|
||||
maxlength="200"
|
||||
autocomplete="off"
|
||||
placeholder="Filter by text"
|
||||
/>
|
||||
<select class="quoteLengthFilter"></select>
|
||||
</div>
|
||||
<div id="extraResults">No search results</div>
|
||||
<div id="quoteSearchResults" class="quoteSearchResults"></div>
|
||||
</div>
|
||||
|
|
Loading…
Reference in a new issue