This repository has been archived on 2023-03-18. You can view files and clone it, but cannot push or open issues or pull requests.
osr-discourse-src/app/assets/javascripts/discourse/widgets/search-menu.js.es6
Sam Saffron 4dcc5f16f1 FEATURE: when under extreme load disable search
The global setting disable_search_queue_threshold
(DISCOURSE_DISABLE_SEARCH_QUEUE_THRESHOLD) which default to 1 second was
added.

This protection ensures that when the application is unable to keep up with
requests it will simply turn off search till it is not backed up.

To disable this protection set this to 0.
2019-07-02 11:22:01 +10:00

325 lines
8.0 KiB
JavaScript

import { popupAjaxError } from "discourse/lib/ajax-error";
import { searchForTerm, isValidSearchTerm } from "discourse/lib/search";
import { createWidget } from "discourse/widgets/widget";
import { h } from "virtual-dom";
import DiscourseURL from "discourse/lib/url";
const searchData = {};
export function initSearchData() {
searchData.loading = false;
searchData.results = {};
searchData.noResults = false;
searchData.term = undefined;
searchData.typeFilter = null;
searchData.invalidTerm = false;
searchData.topicId = null;
}
initSearchData();
// Helps with debouncing and cancelling promises
const SearchHelper = {
_activeSearch: null,
_cancelSearch: null,
// for cancelling debounced search
cancel() {
if (this._activeSearch) {
this._activeSearch.abort();
}
this._cancelSearch = true;
Ember.run.later(() => (this._cancelSearch = false), 400);
},
perform(widget) {
if (this._cancelSearch) {
this._cancelSearch = null;
return;
}
if (this._activeSearch) {
this._activeSearch.abort();
this._activeSearch = null;
}
const { term, typeFilter, contextEnabled } = searchData;
const searchContext = contextEnabled ? widget.searchContext() : null;
const fullSearchUrl = widget.fullSearchUrl();
if (!isValidSearchTerm(term)) {
searchData.noResults = true;
searchData.results = [];
searchData.loading = false;
searchData.invalidTerm = true;
widget.scheduleRerender();
} else {
searchData.invalidTerm = false;
this._activeSearch = searchForTerm(term, {
typeFilter,
searchContext,
fullSearchUrl
});
this._activeSearch
.then(content => {
searchData.noResults = content.resultTypes.length === 0;
if (content.grouped_search_result) {
searchData.term = content.grouped_search_result.term;
}
searchData.results = content;
if (searchContext && searchContext.type === "topic") {
widget.appEvents.trigger("post-stream:refresh", { force: true });
searchData.topicId = searchContext.id;
} else {
searchData.topicId = null;
}
})
.catch(popupAjaxError)
.finally(() => {
searchData.loading = false;
widget.scheduleRerender();
this._activeSearch = null;
});
}
}
};
export default createWidget("search-menu", {
tagName: "div.search-menu",
searchData,
fullSearchUrl(opts) {
const contextEnabled = searchData.contextEnabled;
const ctx = contextEnabled ? this.searchContext() : null;
const type = ctx ? Ember.get(ctx, "type") : null;
let url = "/search";
const params = [];
if (searchData.term) {
let query = "";
query += `q=${encodeURIComponent(searchData.term)}`;
if (contextEnabled && ctx) {
if (
this.currentUser &&
ctx.id.toString().toLowerCase() ===
this.currentUser.get("username_lower") &&
type === "private_messages"
) {
query += " in:private";
} else {
query += encodeURIComponent(" " + type + ":" + ctx.id);
}
}
if (query) params.push(query);
}
if (opts && opts.expanded) params.push("expanded=true");
if (params.length > 0) {
url = `${url}?${params.join("&")}`;
}
return Discourse.getURL(url);
},
panelContents() {
const contextEnabled = searchData.contextEnabled;
let searchInput = [
this.attach("search-term", { value: searchData.term, contextEnabled })
];
if (searchData.term && searchData.loading) {
searchInput.push(h("div.searching", h("div.spinner")));
}
const results = [
h("div.search-input", searchInput),
this.attach("search-context", {
contextEnabled,
url: this.fullSearchUrl({ expanded: true })
})
];
if (searchData.term && !searchData.loading) {
results.push(
this.attach("search-menu-results", {
term: searchData.term,
noResults: searchData.noResults,
results: searchData.results,
invalidTerm: searchData.invalidTerm,
searchContextEnabled: searchData.contextEnabled
})
);
}
return results;
},
searchService() {
if (!this._searchService) {
this._searchService = this.register.lookup("search-service:main");
}
return this._searchService;
},
searchContext() {
if (!this._searchContext) {
this._searchContext = this.searchService().get("searchContext");
}
return this._searchContext;
},
html(attrs) {
const searchContext = this.searchContext();
const shouldTriggerSearch =
searchData.contextEnabled !== attrs.contextEnabled ||
(searchContext &&
searchContext.type === "topic" &&
searchData.topicId !== null &&
searchData.topicId !== searchContext.id);
if (shouldTriggerSearch && searchData.term) {
this.triggerSearch();
}
searchData.contextEnabled = attrs.contextEnabled;
return this.attach("menu-panel", {
maxWidth: 500,
contents: () => this.panelContents()
});
},
mouseDownOutside() {
this.sendWidgetAction("toggleSearchMenu");
},
keyDown(e) {
if (e.which === 27 /* escape */) {
this.sendWidgetAction("toggleSearchMenu");
e.preventDefault();
return false;
}
if (searchData.loading || searchData.noResults) {
return;
}
if (e.which === 65 /* a */) {
let focused = $("header .results .search-link:focus");
if (focused.length === 1) {
if ($("#reply-control.open").length === 1) {
// add a link and focus composer
this.appEvents.trigger("composer:insert-text", focused[0].href, {
ensureSpace: true
});
this.appEvents.trigger("header:keyboard-trigger", { type: "search" });
e.preventDefault();
$("#reply-control.open textarea").focus();
return false;
}
}
}
const up = e.which === 38;
const down = e.which === 40;
if (up || down) {
let focused = $("header .panel-body *:focus")[0];
if (!focused) {
return;
}
let links = $("header .panel-body .results a");
let results = $("header .panel-body .results .search-link");
let prevResult;
let result;
links.each((idx, item) => {
if ($(item).hasClass("search-link")) {
prevResult = item;
}
if (item === focused) {
result = prevResult;
}
});
let index = -1;
if (result) {
index = results.index(result);
}
if (index === -1 && down) {
$("header .panel-body .search-link:first").focus();
} else if (index === 0 && up) {
$("header .panel-body input:first").focus();
} else if (index > -1) {
index += down ? 1 : -1;
if (index >= 0 && index < results.length) {
$(results[index]).focus();
}
}
e.preventDefault();
return false;
}
},
triggerSearch() {
searchData.noResults = false;
this.searchService().set("highlightTerm", searchData.term);
searchData.loading = true;
Ember.run.debounce(SearchHelper, SearchHelper.perform, this, 400);
},
moreOfType(type) {
searchData.typeFilter = type;
this.triggerSearch();
},
searchContextChanged(enabled) {
// This indicates the checkbox has been clicked, NOT that the context has changed.
searchData.typeFilter = null;
this.sendWidgetAction("searchMenuContextChanged", enabled);
searchData.contextEnabled = enabled;
this.triggerSearch();
},
searchTermChanged(term) {
searchData.typeFilter = null;
searchData.term = term;
this.triggerSearch();
},
fullSearch() {
if (!isValidSearchTerm(searchData.term)) {
return;
}
searchData.results = [];
searchData.loading = false;
SearchHelper.cancel();
const url = this.fullSearchUrl();
if (url) {
this.sendWidgetEvent("linkClicked");
DiscourseURL.routeTo(url);
}
}
});