https://meta.discourse.org/t/using-the-search-this-topic-check-box-blocks-search-on-other-pages/56832/6?u=tgxworld
323 lines
7.9 KiB
JavaScript
323 lines
7.9 KiB
JavaScript
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;
|
|
}
|
|
})
|
|
.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;
|
|
|
|
if (contextEnabled && type === "topic") {
|
|
return;
|
|
}
|
|
|
|
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()
|
|
});
|
|
},
|
|
|
|
clickOutside() {
|
|
this.sendWidgetAction("toggleSearchMenu");
|
|
},
|
|
|
|
keyDown(e) {
|
|
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);
|
|
} else if (searchData.contextEnabled) {
|
|
this.triggerSearch();
|
|
}
|
|
}
|
|
});
|