Skip to content

Commit 9c8a8b9

Browse files
committed
Elasticsearch updates to community
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.
1 parent fb2b583 commit 9c8a8b9

11 files changed

+376
-241
lines changed

README.md

+2-3
Original file line numberDiff line numberDiff line change
@@ -161,17 +161,16 @@ Choco-theme contains many external libraries in which it depends on for various
161161
| 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: |
162162
| Canvas Confetti | :clock3: | :clock3: | :clock3: | :clock3: | :clock3: | :heavy_minus_sign: | :clock3: | :heavy_minus_sign: | :heavy_minus_sign: |
163163
| 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: |
164-
| 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: |
164+
| 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: |
165165
| 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: |
166166
| 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: |
167167
| 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: |
168168
| 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: |
169169
| 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: |
170-
| 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: |
170+
| 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: |
171171
| 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: |
172172
| 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: |
173173
| 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: |
174-
| 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: |
175174
| 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: |
176175
| 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: |
177176
| 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: |

js/chocolatey-announcements.js

+6-6
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import { getCookie, setCookieExpirationNever } from './util/chocolatey-functions
22

33
(() => {
44
// Show/Hide right side announcement bar notification badge
5-
const announcementCookie = document.getElementById('announcementCookie').value;
5+
const announcementCookie = document.getElementById('announcementCookie');
66
const announcementCount = document.getElementById('announcementCount');
77
const announcementBadges = document.querySelectorAll('.notification-badge-announcements');
88
const announcementBtns = document.querySelectorAll('.btn-announcement-notifications');
99

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

1818
announcementBtns.forEach(el => {
1919
el.addEventListener('click', () => {
20-
if (!getCookie(announcementCookie)) {
20+
if (!getCookie(announcementCookie.value)) {
2121
if (~location.hostname.indexOf('chocolatey.org')) {
22-
document.cookie = `${announcementCookie}=true; ${setCookieExpirationNever()}path=/; domain=chocolatey.org;`;
22+
document.cookie = `${announcementCookie.value}=true; ${setCookieExpirationNever()}path=/; domain=chocolatey.org;`;
2323
} else {
24-
document.cookie = `${announcementCookie}=true; ${setCookieExpirationNever()}path=/;`;
24+
document.cookie = `${announcementCookie.value}=true; ${setCookieExpirationNever()}path=/;`;
2525
}
2626

2727
for (const i of announcementBadges) {

js/chocolatey-packages.js

-51
Original file line numberDiff line numberDiff line change
@@ -74,57 +74,6 @@ import { getCookie, setCookieExpirationNever, truncateResults } from './util/cho
7474
}, false);
7575
}
7676

77-
// Set tag links on list page
78-
const packageTags = document.querySelectorAll('.package-tag');
79-
packageTags.forEach(el => {
80-
const tag = el.getAttribute('data-package-tag');
81-
let query;
82-
83-
if (window.location.search) {
84-
// Only search in approved packages
85-
if (window.location.search.includes('moderatorQueue=true')) {
86-
query = window.location.search.replace('moderatorQueue=true', 'moderatorQueue=false');
87-
} else {
88-
query = window.location.search;
89-
}
90-
} else {
91-
query = '?';
92-
}
93-
94-
// Only append tag to query if it doesn't already exist
95-
if (query.includes(`tags=${tag}&`)) {
96-
el.href = `/packages${query}`;
97-
} else if (query.endsWith(`tags=${tag}`)) {
98-
el.href = `/packages${query}`;
99-
} else {
100-
el.href = `/packages${query}&tags=${tag}`;
101-
}
102-
});
103-
104-
// Package Filtering
105-
/* const packageFilters = document.querySelectorAll('.package-filter'),
106-
packageSearchTerms = document.querySelectorAll('.selected-search-term');
107-
108-
if (packageFilters) {
109-
for (const i of packageFilters) {
110-
i.onchange = function() {submitPackageFilterForm(i)};
111-
}
112-
}
113-
114-
if (packageSearchTerms) {
115-
for (const i of packageSearchTerms) {
116-
i.onchange = function() {submitPackageFilterForm(i)};
117-
}
118-
}
119-
120-
function submitPackageFilterForm(filter) {
121-
filter.closest('form').submit();
122-
} */
123-
124-
jQuery('#sortOrder,#prerelease,#moderatorQueue,#moderationStatus,.selected-search-term').change(e => {
125-
jQuery(e.currentTarget).closest('form').submit();
126-
});
127-
12877
// Prism for Description section
12978
const descriptionCode = document.querySelectorAll('#description pre');
13079
if (descriptionCode) {

js/chocolatey-search.js

+189
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,107 @@
1+
import autoComplete from '@tarekraafat/autocomplete.js';
12
const Mousetrap = require('mousetrap');
23

34
(() => {
5+
const autoCompleteInput = document.querySelector('#autoComplete');
6+
7+
if (autoCompleteInput) {
8+
autoCompleteInput.addEventListener('init', () => {
9+
// Hiding the input until it is ready to avoid showing it unstyled
10+
document.querySelector('.search-box').style.opacity = 1;
11+
});
12+
13+
const autoCompleteJS = new autoComplete({ // eslint-disable-line new-cap
14+
name: 'autoComplete',
15+
placeHolder: 'Search packages or get suggestions...',
16+
submit: false,
17+
debounce: 300,
18+
data: {
19+
src: async query => {
20+
try {
21+
// Fetch Data from external Source
22+
const source = await fetch(`${window.location.protocol}//${window.location.host}/json/JsonApi?invoke&action=GetSuggestions&SearchTerm=${query}`);
23+
24+
// Data should be an array of `Objects` or `Strings`
25+
const data = await source.json();
26+
27+
return data;
28+
} catch (error) {
29+
return error;
30+
}
31+
},
32+
keys: ['PackageId'] // Data source 'Object' key to be searched
33+
},
34+
resultsList: {
35+
element: (list, data) => {
36+
const templateHeader = document.createElement('div');
37+
const templateNoSuggestions = document.createElement('li');
38+
let templateHelp = '';
39+
const prefixed = ['tag:', 'author:', 'id:'];
40+
41+
templateHeader.classList.add('autocomplete-header');
42+
templateNoSuggestions.classList.add('autocomplete-no-suggestions', 'mt-4');
43+
44+
// Check if any of the keys in 'prefixed' array are present in the user input
45+
const containsPrefixedKey = prefixed.some(key => data.query.includes(key));
46+
47+
if (containsPrefixedKey) {
48+
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>';
49+
} else if (data.results.length === 0) {
50+
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>`;
51+
}
52+
53+
if (data.results.length !== 0) {
54+
templateHelp = `
55+
<div class="d-flex align-items-center justify-content-sm-center text-bg-theme-elevation-1 px-3 py-2 small border-bottom">
56+
<p class="mb-0">Press <kbd>Enter</kbd> to do a full search or select a suggestion below.</p>
57+
</div>
58+
<p class="text-primary ps-4 pe-3 mt-4 mb-2"><strong>Suggestions</strong></p>`;
59+
}
60+
61+
templateHeader.innerHTML = `
62+
<div class="d-flex justify-content-between align-items-center text-center text-bg-theme-neutral p-3 small border-bottom">
63+
<p class="mb-0"><strong>id:searchValue</strong><br />search by id</p>
64+
<p class="mb-0 mx-3"><strong>tag:searchValue</strong><br />search by tag</p>
65+
<p class="mb-0"><strong>author:searchValue</strong><br />search by author</p>
66+
</div>
67+
${templateHelp}`;
68+
69+
if (data.results.length === 0) {
70+
list.insertBefore(templateNoSuggestions, templateHeader.nextSibling);
71+
}
72+
73+
list.prepend(templateHeader);
74+
},
75+
noResults: true,
76+
maxResults: 5,
77+
tabSelect: true
78+
},
79+
resultItem: {
80+
element: (item, data) => {
81+
item.innerHTML = `
82+
<p class="mb-0">${data.match}</p>
83+
<p class="mb-0 text-end autocomplete-downloads"><small>${data.value.DownloadCount} downloads</small></p>`;
84+
},
85+
highlight: true
86+
},
87+
events: {
88+
input: {
89+
selection: event => {
90+
const selection = event.detail.selection.value.PackageId;
91+
92+
window.location.href = `${window.location.protocol}//${window.location.host}/packages/${selection}`;
93+
},
94+
focus: event => {
95+
if (event.target.value) {
96+
autoCompleteJS.start(event.target.value);
97+
}
98+
}
99+
}
100+
}
101+
});
102+
}
103+
104+
// Insert search keys in input field
4105
const isMac = navigator.userAgent.indexOf('Mac OS X') != -1;
5106
const searchInput = document.querySelector('.search-box .search-input');
6107
const topNav = document.querySelector('#topNav');
@@ -56,4 +157,92 @@ const Mousetrap = require('mousetrap');
56157
return false;
57158
});
58159
}
160+
161+
// Package filtering
162+
// Set tag links on list page
163+
const packageTags = document.querySelectorAll('.package-tag');
164+
packageTags.forEach(el => {
165+
const tag = el.getAttribute('data-package-tag');
166+
let query;
167+
168+
if (window.location.search) {
169+
// Only search in approved packages
170+
if (window.location.search.includes('moderatorQueue=true')) {
171+
query = window.location.search.replace('moderatorQueue=true', 'moderatorQueue=false');
172+
} else {
173+
query = window.location.search;
174+
}
175+
} else {
176+
query = '?';
177+
}
178+
179+
// Only append tag to query if it doesn't already exist
180+
if (query.includes(`tags=${tag}&`)) {
181+
el.href = `/packages${query}`;
182+
} else if (query.endsWith(`tags=${tag}`)) {
183+
el.href = `/packages${query}`;
184+
} else {
185+
el.href = `/packages${query}&tags=${tag}`;
186+
}
187+
});
188+
189+
const sortOrderOptions = {
190+
relevance: 'relevance',
191+
popularity: 'package-download-count',
192+
alphabetical: 'package-title',
193+
recent: 'package-created'
194+
};
195+
196+
// Trigger search and selection
197+
const searchTriggers = document.querySelectorAll('.trigger-search');
198+
searchTriggers.forEach(trigger => {
199+
trigger.addEventListener('change', e => {
200+
const currentForm = e.target.closest('form');
201+
const hiddenSortOrder = currentForm.querySelector('[name="sortOrder"]');
202+
203+
// If no search term, and the "relevance" sort order is selected
204+
if (e.target.classList.contains('selected-search-term-query') && hiddenSortOrder.value === sortOrderOptions.relevance) {
205+
// Change the attribute name so that it doesn't submit with the form and the value defaults to what is set by the db
206+
hiddenSortOrder.setAttribute('name', '');
207+
}
208+
209+
currentForm.submit();
210+
});
211+
});
212+
213+
// Trigger search on enter in search box
214+
const searchBox = document.querySelector('.search-box');
215+
searchBox.addEventListener('keydown', e => {
216+
if (e.key !== 'Enter') {
217+
return;
218+
}
219+
220+
const currentQuery = window.location.search || '';
221+
const currentQueryParams = new URLSearchParams(currentQuery);
222+
const existingQuery = currentQueryParams.get('q') || '';
223+
const existingSortOrder = currentQueryParams.get('sortOrder') || '';
224+
const inputValue = e.target.value.trim();
225+
226+
if (existingQuery !== inputValue) {
227+
currentQueryParams.set('q', inputValue);
228+
229+
// If no search term, and the "relevance" sort order is selected
230+
if (inputValue === '' && existingSortOrder === sortOrderOptions.relevance) {
231+
// Delete this so the value defaults to what is set by the db
232+
currentQueryParams.delete('sortOrder');
233+
}
234+
}
235+
236+
// Manually construct the query string to ensure 'q=' comes first
237+
let newQuery = `q=${currentQueryParams.get('q')}`;
238+
currentQueryParams.delete('q');
239+
240+
if (currentQueryParams.toString() !== '') {
241+
newQuery += `&${currentQueryParams.toString()}`;
242+
}
243+
244+
const newUrl = `${window.location.protocol}//${window.location.host}/packages?${newQuery}`;
245+
246+
window.location.href = newUrl;
247+
});
59248
})();

js/lib/elasticsearch.js

-68
This file was deleted.

js/lib/typeahead.bundle.min.js

-7
This file was deleted.

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@splidejs/splide": "^4.1.4",
2828
"@splidejs/splide-extension-auto-scroll": "^0.5.3",
2929
"@splidejs/splide-extension-intersection": "^0.2.0",
30+
"@tarekraafat/autocomplete.js": "^10.2.7",
3031
"@types/bootstrap": "5.2.6",
3132
"@types/luxon": "^3.3.0",
3233
"@types/prismjs": "^1.26.0",

0 commit comments

Comments
 (0)