Skip to content

Commit 21806db

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 67f5bf7 commit 21806db

11 files changed

+393
-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

+206
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,109 @@ 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+
const searchParams = new URLSearchParams(window.location.search);
170+
171+
// Only search in approved packages
172+
if (searchParams.get('moderatorQueue') === 'true') {
173+
searchParams.set('moderatorQueue', 'false');
174+
}
175+
176+
// Reset paging back to 1
177+
if (searchParams.has('page')) {
178+
// Replace the existing "page" parameter with "page=1"
179+
searchParams.delete('page');
180+
}
181+
182+
query = `?${searchParams.toString()}`;
183+
} else {
184+
query = '?';
185+
}
186+
187+
// Only append tag to query if it doesn't already exist
188+
if (query.includes(`tags=${tag}&`)) {
189+
el.href = `/packages${query}`;
190+
} else if (query.endsWith(`tags=${tag}`)) {
191+
el.href = `/packages${query}`;
192+
} else {
193+
el.href = `/packages${query}&tags=${tag}`;
194+
}
195+
});
196+
197+
const sortOrderOptions = {
198+
relevance: 'relevance',
199+
popularity: 'package-download-count',
200+
alphabetical: 'package-title',
201+
recent: 'package-created'
202+
};
203+
204+
// Trigger search and selection
205+
const searchTriggers = document.querySelectorAll('.trigger-search');
206+
searchTriggers.forEach(trigger => {
207+
trigger.addEventListener('change', e => {
208+
const currentForm = e.target.closest('form');
209+
const hiddenSortOrder = currentForm.querySelector('[name="sortOrder"]');
210+
const hiddenPageNumber = currentForm.querySelector('[name="page"]');
211+
212+
// If no search term, and the "relevance" sort order is selected
213+
if (e.target.classList.contains('selected-search-term-query') && hiddenSortOrder.value === sortOrderOptions.relevance) {
214+
// Change the attribute name so that it doesn't submit with the form and the value defaults to what is set by the db
215+
hiddenSortOrder.setAttribute('name', '');
216+
}
217+
218+
if (hiddenPageNumber && hiddenPageNumber.value !== 1) {
219+
// Change the attribute name so that it doesn't submit with the form and the value defaults to what is set by the db
220+
hiddenPageNumber.setAttribute('name', '');
221+
}
222+
223+
currentForm.submit();
224+
});
225+
});
226+
227+
// Trigger search on enter in search box
228+
const searchBox = document.querySelector('.search-box');
229+
searchBox.addEventListener('keydown', e => {
230+
if (e.key !== 'Enter') {
231+
return;
232+
}
233+
234+
const currentQuery = window.location.search || '';
235+
const currentQueryParams = new URLSearchParams(currentQuery);
236+
const existingQuery = currentQueryParams.get('q') || '';
237+
const existingSortOrder = currentQueryParams.get('sortOrder') || '';
238+
const inputValue = e.target.value.trim();
239+
240+
if (existingQuery !== inputValue) {
241+
currentQueryParams.set('q', inputValue);
242+
243+
// If no search term, and the "relevance" sort order is selected
244+
if (inputValue === '' && existingSortOrder === sortOrderOptions.relevance) {
245+
// Delete this so the value defaults to what is set by the db
246+
currentQueryParams.delete('sortOrder');
247+
}
248+
}
249+
250+
// Reset the paging back to the first page
251+
currentQueryParams.delete('page');
252+
253+
// Manually construct the query string to ensure 'q=' comes first
254+
let newQuery = `q=${currentQueryParams.get('q')}`;
255+
currentQueryParams.delete('q');
256+
257+
if (currentQueryParams.toString() !== '') {
258+
newQuery += `&${currentQueryParams.toString()}`;
259+
}
260+
261+
const newUrl = `${window.location.protocol}//${window.location.host}/packages?${newQuery}`;
262+
263+
window.location.href = newUrl;
264+
});
59265
})();

0 commit comments

Comments
 (0)