You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

328 lines
9.6 KiB
JavaScript

'use strict';
define('search', [
'translator', 'storage', 'hooks', 'alerts', 'bootstrap',
], function (translator, storage, hooks, alerts, bootstrap) {
const Search = {
current: {},
};
Search.init = function (searchOptions) {
if (!config.searchEnabled) {
return;
}
searchOptions = searchOptions || { in: config.searchDefaultInQuick || 'titles' };
const searchForm = $('[component="search/form"]');
searchForm.each((index, form) => {
init($(form), searchOptions);
});
};
function init(searchForm, searchOptions) {
const searchButton = searchForm.find('[component="search/button"]');
const searchFields = searchForm.find('[component="search/fields"]');
const searchInput = searchFields.find('input[name="query"]');
const quickSearchContainer = searchFields.find('#quick-search-container');
const toggleVisibility = searchFields.hasClass('hidden');
searchForm.find('.advanced-search-link').off('mousedown').on('mousedown', function () {
ajaxify.go('/search');
});
if (toggleVisibility) {
searchInput.off('blur').on('blur', function dismissSearch() {
setTimeout(function () {
if (!searchInput.is(':focus')) {
searchFields.addClass('hidden');
searchButton.removeClass('hidden');
}
}, 200);
});
}
searchInput.off('focus');
const searchElements = {
inputEl: searchInput,
resultEl: quickSearchContainer,
};
Search.enableQuickSearch({
searchOptions: searchOptions,
searchElements: searchElements,
});
searchButton.off('click').on('click', function (e) {
if (!config.loggedIn && !app.user.privileges['search:content']) {
alerts.alert({
message: '[[error:search-requires-login]]',
timeout: 3000,
});
ajaxify.go('login');
return false;
}
e.stopPropagation();
Search.showAndFocusInput(searchForm);
return false;
});
searchForm.off('submit').on('submit', function () {
const input = $(this).find('input[name="query"]');
const data = Search.getSearchPreferences();
data.term = input.val();
data.in = searchOptions.in;
hooks.fire('action:search.submit', {
searchOptions: data,
searchElements: searchElements,
});
Search.query(data, function () {
input.val('');
searchInput.trigger('blur');
});
return false;
});
}
Search.enableQuickSearch = function (options) {
if (!config.searchEnabled || !app.user.privileges['search:content']) {
return;
}
const searchOptions = Object.assign({ in: config.searchDefaultInQuick || 'titles' }, options.searchOptions);
const quickSearchResults = options.searchElements.resultEl;
const inputEl = options.searchElements.inputEl;
let oldValue = inputEl.val();
const filterCategoryEl = quickSearchResults.find('.filter-category');
function updateCategoryFilterName() {
if (ajaxify.data.template.category && ajaxify.data.cid) {
translator.translate('[[search:search-in-category, ' + ajaxify.data.name + ']]', function (translated) {
const name = $('<div></div>').html(translated).text();
filterCategoryEl.find('.name').text(name);
});
}
filterCategoryEl.toggleClass('hidden', !(ajaxify.data.template.category && ajaxify.data.cid));
}
function doSearch() {
options.searchOptions = Object.assign({}, searchOptions);
options.searchOptions.term = inputEl.val();
updateCategoryFilterName();
if (ajaxify.data.template.category && ajaxify.data.cid) {
if (filterCategoryEl.find('input[type="checkbox"]').is(':checked')) {
options.searchOptions.categories = [ajaxify.data.cid];
options.searchOptions.searchChildren = true;
}
}
if (!options.hideDuringSearch) {
quickSearchResults.removeClass('hidden').find('.quick-search-results-container').html('');
quickSearchResults.find('.loading-indicator').removeClass('hidden');
}
hooks.fire('action:search.quick.start', options);
options.searchOptions.searchOnly = 1;
Search.api(options.searchOptions, function (data) {
quickSearchResults.find('.loading-indicator').addClass('hidden');
if (!data.posts || (options.hideOnNoMatches && !data.posts.length)) {
return quickSearchResults.addClass('hidden').find('.quick-search-results-container').html('');
}
data.posts.forEach(function (p) {
const text = $('<div>' + p.content + '</div>').text();
const query = inputEl.val().toLowerCase().replace(/^in:topic-\d+/, '');
const start = Math.max(0, text.toLowerCase().indexOf(query) - 40);
p.snippet = utils.escapeHTML((start > 0 ? '...' : '') +
text.slice(start, start + 80) +
(text.length - start > 80 ? '...' : ''));
});
app.parseAndTranslate('partials/quick-search-results', data, function (html) {
if (html.length) {
html.find('.timeago').timeago();
}
quickSearchResults.toggleClass('hidden', !html.length || !inputEl.is(':focus'))
.find('.quick-search-results-container')
.html(html.length ? html : '');
const highlightEls = quickSearchResults.find(
'.quick-search-results .quick-search-title, .quick-search-results .snippet'
);
Search.highlightMatches(options.searchOptions.term, highlightEls);
hooks.fire('action:search.quick.complete', {
data: data,
options: options,
});
});
});
}
quickSearchResults.find('.filter-category input[type="checkbox"]').on('change', function () {
inputEl.focus();
doSearch();
});
inputEl.off('keyup').on('keyup', utils.debounce(function () {
if (inputEl.val().length < 3) {
quickSearchResults.addClass('hidden');
oldValue = inputEl.val();
return;
}
if (inputEl.val() === oldValue) {
return;
}
oldValue = inputEl.val();
if (!inputEl.is(':focus')) {
return quickSearchResults.addClass('hidden');
}
doSearch();
}, 500));
let mousedownOnResults = false;
quickSearchResults.on('mousedown', '.quick-search-results > *', function () {
$(window).one('mouseup', function () {
quickSearchResults.addClass('hidden');
});
mousedownOnResults = true;
});
inputEl.on('blur', function () {
setTimeout(function () {
if (!inputEl.is(':focus') && !mousedownOnResults && !quickSearchResults.hasClass('hidden')) {
quickSearchResults.addClass('hidden');
}
}, 200);
});
let ajaxified = false;
hooks.on('action:ajaxify.end', function () {
if (!ajaxify.isCold()) {
ajaxified = true;
}
});
inputEl.on('focus', function () {
mousedownOnResults = false;
const query = inputEl.val();
oldValue = query;
if (query && quickSearchResults.find('#quick-search-results').children().length) {
updateCategoryFilterName();
if (ajaxified) {
doSearch();
ajaxified = false;
} else {
quickSearchResults.removeClass('hidden');
}
inputEl[0].setSelectionRange(
query.startsWith('in:topic') ? query.indexOf(' ') + 1 : 0,
query.length
);
}
});
inputEl.off('refresh').on('refresh', function () {
doSearch();
});
};
Search.showAndFocusInput = function (form) {
const parentDropdown = form.parents('.dropdown-menu');
if (parentDropdown.length) { // handle if form is inside a dropdown aka harmony
const toggle = parentDropdown.siblings('[data-bs-toggle]');
const dropdownEl = bootstrap.Dropdown.getOrCreateInstance(toggle[0]);
if (dropdownEl) {
dropdownEl.show();
}
} else { // persona and others
form.find('[component="search/fields"]').removeClass('hidden');
form.find('[component="search/button"]').addClass('hidden');
form.find('[component="search/fields"] input[name="query"]').trigger('focus');
}
};
Search.query = function (data, callback) {
callback = callback || function () {};
ajaxify.go('search?' + createQueryString(data), callback);
};
Search.api = function (data, callback) {
const apiURL = config.relative_path + '/api/search?' + createQueryString(data);
if (data.hasOwnProperty('searchOnly')) {
delete data.searchOnly;
}
const searchURL = config.relative_path + '/search?' + createQueryString(data);
$.get(apiURL, function (result) {
result.url = searchURL;
callback(result);
});
};
function createQueryString(data) {
const searchIn = data.in || 'titles';
let term = data.term.replace(/^[ ?#]*/, '');
try {
term = encodeURIComponent(term);
} catch (e) {
return alerts.error('[[error:invalid-search-term]]');
}
const query = {
...data,
term: term,
in: searchIn,
};
hooks.fire('action:search.createQueryString', {
query: query,
data: data,
});
return decodeURIComponent($.param(query));
}
Search.getSearchPreferences = function () {
try {
return JSON.parse(storage.getItem('search-preferences') || '{}');
} catch (e) {
return {};
}
};
Search.highlightMatches = function (searchQuery, els) {
if (!searchQuery || !els.length) {
return;
}
searchQuery = utils.escapeHTML(searchQuery.replace(/^"/, '').replace(/"$/, '').trim());
const regexStr = searchQuery.split(' ')
.filter(word => word.length > 1)
.map(function (word) { return utils.escapeRegexChars(word); })
.join('|');
const regex = new RegExp('(' + regexStr + ')', 'gi');
els.each(function () {
const result = $(this);
const nested = [];
result.find('*').each(function () {
$(this).after('<!-- ' + nested.length + ' -->');
nested.push($('<div></div>').append($(this)));
});
result.html(result.html().replace(regex, function (match, p1) {
return '<strong class="search-match fw-bold text-decoration-underline">' + p1 + '</strong>';
}));
nested.forEach(function (nestedEl, i) {
result.html(result.html().replace('<!-- ' + i + ' -->', function () {
return nestedEl.html();
}));
});
});
$('.search-result-text').find('img:not(.not-responsive)').addClass('img-fluid');
};
return Search;
});