Skip to content

Commit

Permalink
Elasticsearch updates to community
Browse files Browse the repository at this point in the history
This implements new functionality to the Community repository using by
adding a new library called autoComplete.js. This will offer suggestions
when typing on community.chocolatey.org. While the initial look of the
search box is generally the same, many changes went in to add specific
styling other than the default autoComplete.js provided.
  • Loading branch information
st3phhays committed Jan 26, 2024
1 parent 91a84bd commit 70c4636
Show file tree
Hide file tree
Showing 11 changed files with 376 additions and 241 deletions.
5 changes: 2 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -161,17 +161,16 @@ Choco-theme contains many external libraries in which it depends on for various
| AnchorJS | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_check_mark: | :heavy_check_mark: | :grey_question: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_check_mark: | :heavy_minus_sign: |
| Canvas Confetti | :clock3: | :clock3: | :clock3: | :clock3: | :clock3: | :heavy_minus_sign: | :clock3: | :heavy_minus_sign: | :heavy_minus_sign: |
| DOCSEARCH (Algolia) | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| Elasticsearch | :heavy_minus_sign: | :grey_question: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| autoComplete.js | :heavy_minus_sign: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| EasyMDE | :heavy_minus_sign: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :grey_question: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| Mousetrap | :heavy_minus_sign: | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :grey_question: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| Knockout | :heavy_minus_sign: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| Lite YouTube Embed | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :grey_question: | :heavy_minus_sign: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: |
| Marked | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :grey_question: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| noUiSlider | :clock3: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :grey_question: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| noUiSlider | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :grey_question: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| Add-to-Calendar Button | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :grey_question: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| Prism | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_check_mark: | :heavy_minus_sign: |
| Splide | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| typeahead.js | :heavy_minus_sign: | :clock3: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| jQuery Validation | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :grey_question: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| jQuery Validation Unobtrusive | :heavy_check_mark: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :grey_question: | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
| Balance Text | :heavy_check_mark: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: | :heavy_minus_sign: |
Expand Down
12 changes: 6 additions & 6 deletions js/chocolatey-announcements.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ import { getCookie, setCookieExpirationNever } from './util/chocolatey-functions

(() => {
// Show/Hide right side announcement bar notification badge
const announcementCookie = document.getElementById('announcementCookie').value;
const announcementCookie = document.getElementById('announcementCookie');
const announcementCount = document.getElementById('announcementCount');
const announcementBadges = document.querySelectorAll('.notification-badge-announcements');
const announcementBtns = document.querySelectorAll('.btn-announcement-notifications');

if (announcementCount) {
if (!getCookie(announcementCookie)) {
if (announcementCount && announcementCookie) {
if (!getCookie(announcementCookie.value)) {
for (const i of announcementBadges) {
i.innerText = announcementCount.value;
i.classList.remove('d-none');
Expand All @@ -17,11 +17,11 @@ import { getCookie, setCookieExpirationNever } from './util/chocolatey-functions

announcementBtns.forEach(el => {
el.addEventListener('click', () => {
if (!getCookie(announcementCookie)) {
if (!getCookie(announcementCookie.value)) {
if (~location.hostname.indexOf('chocolatey.org')) {
document.cookie = `${announcementCookie}=true; ${setCookieExpirationNever()}path=/; domain=chocolatey.org;`;
document.cookie = `${announcementCookie.value}=true; ${setCookieExpirationNever()}path=/; domain=chocolatey.org;`;
} else {
document.cookie = `${announcementCookie}=true; ${setCookieExpirationNever()}path=/;`;
document.cookie = `${announcementCookie.value}=true; ${setCookieExpirationNever()}path=/;`;
}

for (const i of announcementBadges) {
Expand Down
51 changes: 0 additions & 51 deletions js/chocolatey-packages.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,57 +74,6 @@ import { getCookie, setCookieExpirationNever, truncateResults } from './util/cho
}, false);
}

// Set tag links on list page
const packageTags = document.querySelectorAll('.package-tag');
packageTags.forEach(el => {
const tag = el.getAttribute('data-package-tag');
let query;

if (window.location.search) {
// Only search in approved packages
if (window.location.search.includes('moderatorQueue=true')) {
query = window.location.search.replace('moderatorQueue=true', 'moderatorQueue=false');
} else {
query = window.location.search;
}
} else {
query = '?';
}

// Only append tag to query if it doesn't already exist
if (query.includes(`tags=${tag}&`)) {
el.href = `/packages${query}`;
} else if (query.endsWith(`tags=${tag}`)) {
el.href = `/packages${query}`;
} else {
el.href = `/packages${query}&tags=${tag}`;
}
});

// Package Filtering
/* const packageFilters = document.querySelectorAll('.package-filter'),
packageSearchTerms = document.querySelectorAll('.selected-search-term');
if (packageFilters) {
for (const i of packageFilters) {
i.onchange = function() {submitPackageFilterForm(i)};
}
}
if (packageSearchTerms) {
for (const i of packageSearchTerms) {
i.onchange = function() {submitPackageFilterForm(i)};
}
}
function submitPackageFilterForm(filter) {
filter.closest('form').submit();
} */

jQuery('#sortOrder,#prerelease,#moderatorQueue,#moderationStatus,.selected-search-term').change(e => {
jQuery(e.currentTarget).closest('form').submit();
});

// Prism for Description section
const descriptionCode = document.querySelectorAll('#description pre');
if (descriptionCode) {
Expand Down
189 changes: 189 additions & 0 deletions js/chocolatey-search.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,107 @@
import autoComplete from '@tarekraafat/autocomplete.js';
const Mousetrap = require('mousetrap');

(() => {
const autoCompleteInput = document.querySelector('#autoComplete');

if (autoCompleteInput) {
autoCompleteInput.addEventListener('init', () => {
// Hiding the input until it is ready to avoid showing it unstyled
document.querySelector('.search-box').style.opacity = 1;
});

const autoCompleteJS = new autoComplete({ // eslint-disable-line new-cap
name: 'autoComplete',
placeHolder: 'Search packages or get suggestions...',
submit: false,
debounce: 300,
data: {
src: async query => {
try {
// Fetch Data from external Source
const source = await fetch(`${window.location.protocol}//${window.location.host}/json/JsonApi?invoke&action=GetSuggestions&SearchTerm=${query}`);

// Data should be an array of `Objects` or `Strings`
const data = await source.json();

return data;
} catch (error) {
return error;
}
},
keys: ['PackageId'] // Data source 'Object' key to be searched
},
resultsList: {
element: (list, data) => {
const templateHeader = document.createElement('div');
const templateNoSuggestions = document.createElement('li');
let templateHelp = '';
const prefixed = ['tag:', 'author:', 'id:'];

templateHeader.classList.add('autocomplete-header');
templateNoSuggestions.classList.add('autocomplete-no-suggestions', 'mt-4');

// Check if any of the keys in 'prefixed' array are present in the user input
const containsPrefixedKey = prefixed.some(key => data.query.includes(key));

if (containsPrefixedKey) {
templateNoSuggestions.innerHTML = '<p class="mb-0">Searching with a prefix of <strong>id:</strong>, <strong>tag:</strong>, or <strong>author:</strong> will not display suggestions. Press <kbd>Enter</kbd> to do a full search.</p>';
} else if (data.results.length === 0) {
templateNoSuggestions.innerHTML = `<p class="mb-0">No suggestions for package id "<strong>${data.query}</strong>". Press <kbd>Enter</kbd> to do a full search.</p>`;
}

if (data.results.length !== 0) {
templateHelp = `
<div class="d-flex align-items-center justify-content-sm-center text-bg-theme-elevation-1 px-3 py-2 small border-bottom">
<p class="mb-0">Press <kbd>Enter</kbd> to do a full search or select a suggestion below.</p>
</div>
<p class="text-primary ps-4 pe-3 mt-4 mb-2"><strong>Suggestions</strong></p>`;
}

templateHeader.innerHTML = `
<div class="d-flex justify-content-between align-items-center text-center text-bg-theme-neutral p-3 small border-bottom">
<p class="mb-0"><strong>id:searchValue</strong><br />search by id</p>
<p class="mb-0 mx-3"><strong>tag:searchValue</strong><br />search by tag</p>
<p class="mb-0"><strong>author:searchValue</strong><br />search by author</p>
</div>
${templateHelp}`;

if (data.results.length === 0) {
list.insertBefore(templateNoSuggestions, templateHeader.nextSibling);
}

list.prepend(templateHeader);
},
noResults: true,
maxResults: 5,
tabSelect: true
},
resultItem: {
element: (item, data) => {
item.innerHTML = `
<p class="mb-0">${data.match}</p>
<p class="mb-0 text-end autocomplete-downloads"><small>${data.value.DownloadCount} downloads</small></p>`;
},
highlight: true
},
events: {
input: {
selection: event => {
const selection = event.detail.selection.value.PackageId;

window.location.href = `${window.location.protocol}//${window.location.host}/packages/${selection}`;
},
focus: event => {
if (event.target.value) {
autoCompleteJS.start(event.target.value);
}
}
}
}
});
}

// Insert search keys in input field
const isMac = navigator.userAgent.indexOf('Mac OS X') != -1;
const searchInput = document.querySelector('.search-box .search-input');
const topNav = document.querySelector('#topNav');
Expand Down Expand Up @@ -56,4 +157,92 @@ const Mousetrap = require('mousetrap');
return false;
});
}

// Package filtering
// Set tag links on list page
const packageTags = document.querySelectorAll('.package-tag');
packageTags.forEach(el => {
const tag = el.getAttribute('data-package-tag');
let query;

if (window.location.search) {
// Only search in approved packages
if (window.location.search.includes('moderatorQueue=true')) {
query = window.location.search.replace('moderatorQueue=true', 'moderatorQueue=false');
} else {
query = window.location.search;
}
} else {
query = '?';
}

// Only append tag to query if it doesn't already exist
if (query.includes(`tags=${tag}&`)) {
el.href = `/packages${query}`;
} else if (query.endsWith(`tags=${tag}`)) {
el.href = `/packages${query}`;
} else {
el.href = `/packages${query}&tags=${tag}`;
}
});

const sortOrderOptions = {
relevance: 'relevance',
popularity: 'package-download-count',
alphabetical: 'package-title',
recent: 'package-created'
};

// Trigger search and selection
const searchTriggers = document.querySelectorAll('.trigger-search');
searchTriggers.forEach(trigger => {
trigger.addEventListener('change', e => {
const currentForm = e.target.closest('form');
const hiddenSortOrder = currentForm.querySelector('[name="sortOrder"]');

// If no search term, and the "relevance" sort order is selected
if (e.target.classList.contains('selected-search-term-query') && hiddenSortOrder.value === sortOrderOptions.relevance) {
// Change the attribute name so that it doesn't submit with the form and the value defaults to what is set by the db
hiddenSortOrder.setAttribute('name', '');
}

currentForm.submit();
});
});

// Trigger search on enter in search box
const searchBox = document.querySelector('.search-box');
searchBox.addEventListener('keydown', e => {
if (e.key !== 'Enter') {
return;
}

const currentQuery = window.location.search || '';
const currentQueryParams = new URLSearchParams(currentQuery);
const existingQuery = currentQueryParams.get('q') || '';
const existingSortOrder = currentQueryParams.get('sortOrder') || '';
const inputValue = e.target.value.trim();

if (existingQuery !== inputValue) {
currentQueryParams.set('q', inputValue);

// If no search term, and the "relevance" sort order is selected
if (inputValue === '' && existingSortOrder === sortOrderOptions.relevance) {
// Delete this so the value defaults to what is set by the db
currentQueryParams.delete('sortOrder');
}
}

// Manually construct the query string to ensure 'q=' comes first
let newQuery = `q=${currentQueryParams.get('q')}`;
currentQueryParams.delete('q');

if (currentQueryParams.toString() !== '') {
newQuery += `&${currentQueryParams.toString()}`;
}

const newUrl = `${window.location.protocol}//${window.location.host}/packages?${newQuery}`;

window.location.href = newUrl;
});
})();
68 changes: 0 additions & 68 deletions js/lib/elasticsearch.js

This file was deleted.

7 changes: 0 additions & 7 deletions js/lib/typeahead.bundle.min.js

This file was deleted.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
"@splidejs/splide": "^4.1.4",
"@splidejs/splide-extension-auto-scroll": "^0.5.3",
"@splidejs/splide-extension-intersection": "^0.2.0",
"@tarekraafat/autocomplete.js": "^10.2.7",
"@types/bootstrap": "5.2.6",
"@types/luxon": "^3.3.0",
"@types/prismjs": "^1.26.0",
Expand Down
Loading

0 comments on commit 70c4636

Please sign in to comment.