diff options
Diffstat (limited to 'pydis_site/static/js/resources/resources.js')
-rw-r--r-- | pydis_site/static/js/resources/resources.js | 114 |
1 files changed, 98 insertions, 16 deletions
diff --git a/pydis_site/static/js/resources/resources.js b/pydis_site/static/js/resources/resources.js index 508849e1..d6cc8128 100644 --- a/pydis_site/static/js/resources/resources.js +++ b/pydis_site/static/js/resources/resources.js @@ -8,6 +8,13 @@ var activeFilters = { difficulty: [] }; +// Options for fuzzysort +const fuzzysortOptions = { + allowTypo: true, // Allow our users to make typos + titleThreshold: -10000, // The threshold for the fuzziness on title matches. Closer to 0 is stricter. + descriptionThreshold: -500, // The threshold for the fuzziness on description matches. +}; + /* Add a filter, and update the UI */ function addFilter(filterName, filterItem) { var filterIndex = activeFilters[filterName].indexOf(filterItem); @@ -25,6 +32,7 @@ function removeAllFilters() { "payment-tiers": [], difficulty: [] }; + $("#resource-search input").val(""); updateUI(); } @@ -51,6 +59,13 @@ function noFilters() { function deserializeURLParams() { let searchParams = new window.URLSearchParams(window.location.search); + // Add the search query to the search bar. + if (searchParams.has("search")) { + let searchQuery = searchParams.get("search"); + $("#resource-search input").val(searchQuery); + $(".close-filters-button").show(); + } + // Work through the parameters and add them to the filter object $.each(Object.keys(activeFilters), function(_, filterType) { let paramFilterContent = searchParams.get(filterType); @@ -62,11 +77,13 @@ function deserializeURLParams() { // Update the corresponding filter UI, so it reflects the internal state. let filterAdded = false; $(paramFilterArray).each(function(_, filter) { - // Make sure the filter is valid before we do anything. + // Catch special cases. if (String(filter) === "rickroll" && filterType === "type") { window.location.href = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"; } else if (String(filter) === "sneakers" && filterType === "topics") { window.location.href = "https://www.youtube.com/watch?v=NNZscmNE9QI"; + + // If the filter is valid, mirror it to the UI. } else if (validFilters.hasOwnProperty(filterType) && validFilters[filterType].includes(String(filter))) { let checkbox = $(`.filter-checkbox[data-filter-name='${filterType}'][data-filter-item='${filter}']`); let filterTag = $(`.filter-box-tag[data-filter-name='${filterType}'][data-filter-item='${filter}']`); @@ -91,10 +108,23 @@ function deserializeURLParams() { }); } +/* Show or hide the duckies, depending on whether or not there are any resources visible. */ +function updateDuckies() { + let visibleResources = Boolean($(".resource-box:visible").length); + if (!visibleResources) { + $(".no-resources-found").show(); + } else { + $(".no-resources-found").hide(); + } +} + + /* Update the URL with new parameters */ function updateURL() { - // If there's nothing in the filters, we don't want anything in the URL. - if (noFilters()) { + let searchQuery = $("#resource-search input").val(); + + // If there's no active filtering parameters, we can return early. + if (noFilters() && searchQuery.length === 0) { window.history.replaceState(null, document.title, './'); return; } @@ -107,10 +137,44 @@ function updateURL() { } }); + // Add the search query, if necessary. + if (searchQuery.length > 0) { + searchParams.set("search", searchQuery); + } + // Now update the URL window.history.replaceState(null, document.title, `?${searchParams.toString()}`); } +/* Apply search terms */ +function filterBySearch(resourceItems) { + let searchQuery = $("#resource-search input").val(); + + /* Show and update the tag if there's a search query */ + if (searchQuery) { + let tag = $(".tag.search-query"); + let tagText = $(".tag.search-query span"); + tagText.text(`Search: ${searchQuery}`); + tag.show(); + $(".close-filters-button").show(); + } + + resourceItems.filter(function() { + // Get the resource title and description + let title = $(this).attr("data-resource-name"); + let description = $(this).find("p").text(); + + // Run a fuzzy search. Does the title or description match the query? + let titleMatch = fuzzysort.single(searchQuery, title, fuzzysortOptions); + titleMatch = Boolean(titleMatch) && titleMatch.score > fuzzysortOptions.titleThreshold; + + let descriptionMatch = fuzzysort.single(searchQuery, description, fuzzysortOptions); + descriptionMatch = Boolean(descriptionMatch) && descriptionMatch.score > fuzzysortOptions.descriptionThreshold; + + return titleMatch || descriptionMatch; + }).show(); +} + /* Update the resources to match 'active_filters' */ function updateUI() { let resources = $('.resource-box'); @@ -118,19 +182,31 @@ function updateUI() { let resourceTags = $('.resource-tag'); let noTagsSelected = $(".no-tags-selected.tag"); let closeFiltersButton = $(".close-filters-button"); + let searchQuery = $("#resource-search input").val(); + let searchTag = $(".tag.search-query"); // Update the URL to match the new filters. updateURL(); // If there's nothing in the filters, we can return early. if (noFilters()) { - resources.show(); + // If we have a searchQuery, we need to run all resources through a search. + if (searchQuery.length > 0) { + resources.hide(); + noTagsSelected.hide(); + filterBySearch(resources); + } else { + resources.show(); + noTagsSelected.show(); + closeFiltersButton.hide(); + $(".tag.search-query").hide(); + } + filterTags.hide(); - noTagsSelected.show(); - closeFiltersButton.hide(); resourceTags.removeClass("active"); $(`.filter-checkbox:checked`).prop("checked", false); - $(".no-resources-found").hide(); + updateDuckies(); + return; } else { // Hide everything @@ -158,9 +234,8 @@ function updateUI() { } // Otherwise, hide everything and then filter the resources to decide what to show. - let hasMatches = false; resources.hide(); - resources.filter(function() { + let filteredResources = resources.filter(function() { let validation = { topics: false, type: false, @@ -187,20 +262,22 @@ function updateUI() { // If validation passes, show the resource. if (Object.values(validation).every(Boolean)) { - hasMatches = true; return true; } else { return false; } - }).show(); - + }); - // If there are no matches, show the no matches message - if (!hasMatches) { - $(".no-resources-found").show(); + // Run the items we've found through the search filter, if necessary. + if (searchQuery.length > 0) { + filterBySearch(filteredResources); } else { - $(".no-resources-found").hide(); + filteredResources.show(); + searchTag.hide(); } + + // Gotta update those duckies! + updateDuckies(); } // Executed when the page has finished loading. @@ -230,6 +307,11 @@ document.addEventListener("DOMContentLoaded", function () { setTimeout(() => { categoryHeaders.removeClass("no-transition"); }, 10); } + // When you type into the search bar, trigger an UI update. + $("#resource-search input").on("input", function() { + updateUI(); + }); + // If you click on the div surrounding the filter checkbox, it clicks the corresponding checkbox. $('.filter-panel').on("click",function(event) { let hitsCheckbox = Boolean(String(event.target)); |