UX: Revamp quick search (#14499)
Co-authored-by: Robin Ward <robin.ward@gmail.com> Co-authored-by: Alan Guo Xiang Tan <gxtan1990@gmail.com>
This commit is contained in:
parent
98d2836eb4
commit
e9b1d29d8b
|
@ -117,11 +117,8 @@ function translateGroupedSearchResults(results, opts) {
|
||||||
const name = pair[1];
|
const name = pair[1];
|
||||||
if (results[name].length > 0) {
|
if (results[name].length > 0) {
|
||||||
const componentName =
|
const componentName =
|
||||||
opts.searchContext &&
|
opts.showPosts && type === "topic" ? "post" : type;
|
||||||
opts.searchContext.type === "topic" &&
|
|
||||||
type === "topic"
|
|
||||||
? "post"
|
|
||||||
: type;
|
|
||||||
const result = {
|
const result = {
|
||||||
results: results[name],
|
results: results[name],
|
||||||
componentName: `search-result-${componentName}`,
|
componentName: `search-result-${componentName}`,
|
||||||
|
@ -157,12 +154,8 @@ export function searchForTerm(term, opts) {
|
||||||
data.restrict_to_archetype = opts.restrictToArchetype;
|
data.restrict_to_archetype = opts.restrictToArchetype;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (opts.searchContext) {
|
if (term.includes("topic:")) {
|
||||||
data.search_context = {
|
opts.showPosts = true;
|
||||||
type: opts.searchContext.type,
|
|
||||||
id: opts.searchContext.id,
|
|
||||||
name: opts.searchContext.name,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ajaxPromise = ajax("/search/query", { data });
|
let ajaxPromise = ajax("/search/query", { data });
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
import EmberObject, { get } from "@ember/object";
|
import EmberObject, { get } from "@ember/object";
|
||||||
import discourseComputed, { observes } from "discourse-common/utils/decorators";
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
|
||||||
export default EmberObject.extend({
|
export default EmberObject.extend({
|
||||||
searchContextEnabled: false, // checkbox to scope search
|
searchContextEnabled: false, // checkbox to scope search
|
||||||
searchContext: null,
|
searchContext: null,
|
||||||
term: null,
|
|
||||||
highlightTerm: null,
|
highlightTerm: null,
|
||||||
|
|
||||||
@observes("term")
|
|
||||||
_sethighlightTerm() {
|
|
||||||
this.set("highlightTerm", this.term);
|
|
||||||
},
|
|
||||||
|
|
||||||
@discourseComputed("searchContext")
|
@discourseComputed("searchContext")
|
||||||
contextType: {
|
contextType: {
|
||||||
get(searchContext) {
|
get(searchContext) {
|
||||||
|
|
|
@ -1,34 +1,21 @@
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
import { createWidget } from "discourse/widgets/widget";
|
import { createWidget } from "discourse/widgets/widget";
|
||||||
import { get } from "@ember/object";
|
|
||||||
import { h } from "virtual-dom";
|
|
||||||
import { searchContextDescription } from "discourse/lib/search";
|
|
||||||
|
|
||||||
createWidget("search-term", {
|
createWidget("search-term", {
|
||||||
tagName: "input",
|
tagName: "input",
|
||||||
buildId: () => "search-term",
|
buildId: () => "search-term",
|
||||||
buildKey: () => "search-term",
|
buildKey: () => "search-term",
|
||||||
|
|
||||||
defaultState() {
|
|
||||||
return { afterAutocomplete: false };
|
|
||||||
},
|
|
||||||
|
|
||||||
buildAttributes(attrs) {
|
buildAttributes(attrs) {
|
||||||
return {
|
return {
|
||||||
type: "text",
|
type: "text",
|
||||||
value: attrs.value || "",
|
value: attrs.value || "",
|
||||||
autocomplete: "discourse",
|
autocomplete: "off",
|
||||||
placeholder: attrs.contextEnabled ? "" : I18n.t("search.title"),
|
placeholder: I18n.t("search.title"),
|
||||||
"aria-label": I18n.t("search.title"),
|
"aria-label": I18n.t("search.title"),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
keyUp(e) {
|
|
||||||
if (e.key === "Enter" && !this.state.afterAutocomplete) {
|
|
||||||
return this.sendWidgetAction("fullSearch");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
input(e) {
|
input(e) {
|
||||||
const val = this.attrs.value;
|
const val = this.attrs.value;
|
||||||
|
|
||||||
|
@ -41,47 +28,9 @@ createWidget("search-term", {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: No longer used, remove in December 2021
|
||||||
createWidget("search-context", {
|
createWidget("search-context", {
|
||||||
tagName: "div.search-context",
|
html() {
|
||||||
|
return false;
|
||||||
html(attrs) {
|
|
||||||
const service = this.register.lookup("search-service:main");
|
|
||||||
const ctx = service.get("searchContext");
|
|
||||||
|
|
||||||
const result = [];
|
|
||||||
if (ctx) {
|
|
||||||
const description = searchContextDescription(
|
|
||||||
get(ctx, "type"),
|
|
||||||
get(ctx, "user.username") ||
|
|
||||||
get(ctx, "category.name") ||
|
|
||||||
get(ctx, "tag.id")
|
|
||||||
);
|
|
||||||
result.push(
|
|
||||||
h("label", [
|
|
||||||
h("input", { type: "checkbox", checked: attrs.contextEnabled }),
|
|
||||||
" ",
|
|
||||||
description,
|
|
||||||
])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!attrs.contextEnabled) {
|
|
||||||
result.push(
|
|
||||||
this.attach("link", {
|
|
||||||
href: attrs.url,
|
|
||||||
label: "show_help",
|
|
||||||
className: "show-help",
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
},
|
|
||||||
|
|
||||||
click() {
|
|
||||||
const val = $(".search-context input").is(":checked");
|
|
||||||
if (val !== this.attrs.contextEnabled) {
|
|
||||||
this.sendWidgetAction("searchContextChanged", val);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { escapeExpression, formatUsername } from "discourse/lib/utilities";
|
import { escapeExpression, formatUsername } from "discourse/lib/utilities";
|
||||||
|
import { deepMerge } from "discourse-common/lib/object";
|
||||||
import I18n from "I18n";
|
import I18n from "I18n";
|
||||||
import RawHtml from "discourse/widgets/raw-html";
|
import RawHtml from "discourse/widgets/raw-html";
|
||||||
import { avatarImg } from "discourse/widgets/post";
|
import { avatarImg } from "discourse/widgets/post";
|
||||||
|
@ -10,6 +11,10 @@ import { h } from "virtual-dom";
|
||||||
import highlightSearch from "discourse/lib/highlight-search";
|
import highlightSearch from "discourse/lib/highlight-search";
|
||||||
import { iconNode } from "discourse-common/lib/icon-library";
|
import { iconNode } from "discourse-common/lib/icon-library";
|
||||||
import renderTag from "discourse/lib/render-tag";
|
import renderTag from "discourse/lib/render-tag";
|
||||||
|
import {
|
||||||
|
MODIFIER_REGEXP,
|
||||||
|
TOPIC_REPLACE_REGEXP,
|
||||||
|
} from "discourse/widgets/search-menu";
|
||||||
|
|
||||||
const suggestionShortcuts = [
|
const suggestionShortcuts = [
|
||||||
"in:title",
|
"in:title",
|
||||||
|
@ -24,6 +29,29 @@ const suggestionShortcuts = [
|
||||||
"order:latest_topic",
|
"order:latest_topic",
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const QUICK_TIPS = [
|
||||||
|
{
|
||||||
|
label: "#",
|
||||||
|
description: I18n.t("search.tips.category_tag"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "@",
|
||||||
|
description: I18n.t("search.tips.author"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "in:",
|
||||||
|
description: I18n.t("search.tips.in"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "status:",
|
||||||
|
description: I18n.t("search.tips.status"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: I18n.t("search.tips.full_search_key", { modifier: "Ctrl" }),
|
||||||
|
description: I18n.t("search.tips.full_search"),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
export function addSearchSuggestion(value) {
|
export function addSearchSuggestion(value) {
|
||||||
if (suggestionShortcuts.indexOf(value) === -1) {
|
if (suggestionShortcuts.indexOf(value) === -1) {
|
||||||
suggestionShortcuts.push(value);
|
suggestionShortcuts.push(value);
|
||||||
|
@ -33,7 +61,7 @@ export function addSearchSuggestion(value) {
|
||||||
class Highlighted extends RawHtml {
|
class Highlighted extends RawHtml {
|
||||||
constructor(html, term) {
|
constructor(html, term) {
|
||||||
super({ html: `<span>${html}</span>` });
|
super({ html: `<span>${html}</span>` });
|
||||||
this.term = term;
|
this.term = term.replace(TOPIC_REPLACE_REGEXP, "");
|
||||||
}
|
}
|
||||||
|
|
||||||
decorate($html) {
|
decorate($html) {
|
||||||
|
@ -63,7 +91,6 @@ function createSearchResult({ type, linkField, builder }) {
|
||||||
className: "search-link",
|
className: "search-link",
|
||||||
searchResultId,
|
searchResultId,
|
||||||
searchResultType: type,
|
searchResultType: type,
|
||||||
searchContextEnabled: attrs.searchContextEnabled,
|
|
||||||
searchLogId: attrs.searchLogId,
|
searchLogId: attrs.searchLogId,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -95,7 +122,10 @@ createSearchResult({
|
||||||
linkField: "url",
|
linkField: "url",
|
||||||
builder(t) {
|
builder(t) {
|
||||||
const tag = escapeExpression(t.id);
|
const tag = escapeExpression(t.id);
|
||||||
return new RawHtml({ html: renderTag(tag, { tagName: "span" }) });
|
return [
|
||||||
|
iconNode("tag"),
|
||||||
|
new RawHtml({ html: renderTag(tag, { tagName: "span" }) }),
|
||||||
|
];
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -227,84 +257,82 @@ createWidget("search-menu-results", {
|
||||||
tagName: "div.results",
|
tagName: "div.results",
|
||||||
|
|
||||||
html(attrs) {
|
html(attrs) {
|
||||||
if (attrs.suggestionKeyword) {
|
const { term, suggestionKeyword, results, searchTopics } = attrs;
|
||||||
|
|
||||||
|
if (suggestionKeyword) {
|
||||||
return this.attach("search-menu-assistant", {
|
return this.attach("search-menu-assistant", {
|
||||||
fullTerm: attrs.term,
|
term,
|
||||||
suggestionKeyword: attrs.suggestionKeyword,
|
suggestionKeyword,
|
||||||
results: attrs.suggestionResults || [],
|
results: attrs.suggestionResults || [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attrs.invalidTerm) {
|
if (searchTopics && attrs.invalidTerm) {
|
||||||
return h("div.no-results", I18n.t("search.too_short"));
|
return h("div.no-results", I18n.t("search.too_short"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attrs.noResults) {
|
if (searchTopics && attrs.noResults) {
|
||||||
return h("div.no-results", I18n.t("search.no_results"));
|
return h("div.no-results", I18n.t("search.no_results"));
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = attrs.results;
|
if (!term) {
|
||||||
|
return this.attach("search-menu-initial-options", {
|
||||||
|
term,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const resultTypes = results.resultTypes || [];
|
const resultTypes = results.resultTypes || [];
|
||||||
|
|
||||||
const mainResultsContent = [];
|
const mainResultsContent = [];
|
||||||
const usersAndGroups = [];
|
const usersAndGroups = [];
|
||||||
const categoriesAndTags = [];
|
const categoriesAndTags = [];
|
||||||
const usersAndGroupsMore = [];
|
|
||||||
const categoriesAndTagsMore = [];
|
|
||||||
|
|
||||||
const buildMoreNode = (result) => {
|
const buildMoreNode = (result) => {
|
||||||
const more = [];
|
|
||||||
|
|
||||||
const moreArgs = {
|
const moreArgs = {
|
||||||
className: "filter",
|
className: "filter search-link",
|
||||||
contents: () => [I18n.t("more"), "..."],
|
contents: () => [I18n.t("more"), "..."],
|
||||||
};
|
};
|
||||||
|
|
||||||
if (result.moreUrl) {
|
if (result.moreUrl) {
|
||||||
more.push(
|
return this.attach(
|
||||||
this.attach("link", $.extend(moreArgs, { href: result.moreUrl }))
|
"link",
|
||||||
|
deepMerge(moreArgs, {
|
||||||
|
href: result.moreUrl,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
} else if (result.more) {
|
} else if (result.more) {
|
||||||
more.push(
|
return this.attach(
|
||||||
this.attach(
|
"link",
|
||||||
"link",
|
deepMerge(moreArgs, {
|
||||||
$.extend(moreArgs, {
|
action: "moreOfType",
|
||||||
action: "moreOfType",
|
actionParam: result.type,
|
||||||
actionParam: result.type,
|
})
|
||||||
className: "filter filter-type",
|
|
||||||
})
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (more.length) {
|
|
||||||
return more;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const assignContainer = (result, node) => {
|
const assignContainer = (result, node) => {
|
||||||
if (["topic"].includes(result.type)) {
|
if (searchTopics) {
|
||||||
mainResultsContent.push(node);
|
if (["topic"].includes(result.type)) {
|
||||||
}
|
mainResultsContent.push(node);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (["user", "group"].includes(result.type)) {
|
||||||
|
usersAndGroups.push(node);
|
||||||
|
}
|
||||||
|
|
||||||
if (["user", "group"].includes(result.type)) {
|
if (["category", "tag"].includes(result.type)) {
|
||||||
usersAndGroups.push(node);
|
categoriesAndTags.push(node);
|
||||||
usersAndGroupsMore.push(buildMoreNode(result));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (["category", "tag"].includes(result.type)) {
|
|
||||||
categoriesAndTags.push(node);
|
|
||||||
categoriesAndTagsMore.push(buildMoreNode(result));
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
resultTypes.forEach((rt) => {
|
resultTypes.forEach((rt) => {
|
||||||
const resultNodeContents = [
|
const resultNodeContents = [
|
||||||
this.attach(rt.componentName, {
|
this.attach(rt.componentName, {
|
||||||
searchContextEnabled: attrs.searchContextEnabled,
|
|
||||||
searchLogId: attrs.results.grouped_search_result.search_log_id,
|
searchLogId: attrs.results.grouped_search_result.search_log_id,
|
||||||
results: rt.results,
|
results: rt.results,
|
||||||
term: attrs.term,
|
term,
|
||||||
}),
|
}),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -320,31 +348,19 @@ createWidget("search-menu-results", {
|
||||||
|
|
||||||
const content = [];
|
const content = [];
|
||||||
|
|
||||||
if (mainResultsContent.length) {
|
if (!searchTopics) {
|
||||||
content.push(h("div.main-results", mainResultsContent));
|
content.push(this.attach("search-menu-initial-options", { term }));
|
||||||
}
|
} else {
|
||||||
|
if (mainResultsContent.length) {
|
||||||
if (usersAndGroups.length || categoriesAndTags.length) {
|
content.push(mainResultsContent);
|
||||||
const secondaryResultsContents = [];
|
} else {
|
||||||
|
return h("div.no-results", I18n.t("search.no_results"));
|
||||||
secondaryResultsContents.push(usersAndGroups);
|
|
||||||
secondaryResultsContents.push(usersAndGroupsMore);
|
|
||||||
|
|
||||||
if (usersAndGroups.length && categoriesAndTags.length) {
|
|
||||||
secondaryResultsContents.push(h("div.separator"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
secondaryResultsContents.push(categoriesAndTags);
|
|
||||||
secondaryResultsContents.push(categoriesAndTagsMore);
|
|
||||||
|
|
||||||
const secondaryResults = h(
|
|
||||||
"div.secondary-results",
|
|
||||||
secondaryResultsContents
|
|
||||||
);
|
|
||||||
|
|
||||||
content.push(secondaryResults);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
content.push(categoriesAndTags);
|
||||||
|
content.push(usersAndGroups);
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -369,8 +385,8 @@ createWidget("search-menu-assistant", {
|
||||||
}
|
}
|
||||||
|
|
||||||
const content = [];
|
const content = [];
|
||||||
const { fullTerm, suggestionKeyword } = attrs;
|
const { suggestionKeyword, term } = attrs;
|
||||||
let prefix = fullTerm.split(suggestionKeyword)[0].trim() || "";
|
let prefix = term?.split(suggestionKeyword)[0].trim() || "";
|
||||||
|
|
||||||
if (prefix.length) {
|
if (prefix.length) {
|
||||||
prefix = `${prefix} `;
|
prefix = `${prefix} `;
|
||||||
|
@ -388,7 +404,8 @@ createWidget("search-menu-assistant", {
|
||||||
this.attach("search-menu-assistant-item", {
|
this.attach("search-menu-assistant-item", {
|
||||||
prefix,
|
prefix,
|
||||||
category: item.model,
|
category: item.model,
|
||||||
slug: `${prefix}${fullSlug} `,
|
slug: `${prefix}${fullSlug}`,
|
||||||
|
withInLabel: attrs.withInLabel,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
@ -396,7 +413,8 @@ createWidget("search-menu-assistant", {
|
||||||
this.attach("search-menu-assistant-item", {
|
this.attach("search-menu-assistant-item", {
|
||||||
prefix,
|
prefix,
|
||||||
tag: item.name,
|
tag: item.name,
|
||||||
slug: `${prefix}#${item.name} `,
|
slug: `${prefix}#${item.name}`,
|
||||||
|
withInLabel: attrs.withInLabel,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -408,17 +426,17 @@ createWidget("search-menu-assistant", {
|
||||||
this.attach("search-menu-assistant-item", {
|
this.attach("search-menu-assistant-item", {
|
||||||
prefix,
|
prefix,
|
||||||
user,
|
user,
|
||||||
slug: `${prefix}@${user.username} `,
|
slug: `${prefix}@${user.username}`,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
suggestionShortcuts.forEach((item) => {
|
suggestionShortcuts.forEach((item) => {
|
||||||
if (item.includes(suggestionKeyword)) {
|
if (item.includes(suggestionKeyword) || !suggestionKeyword) {
|
||||||
content.push(
|
content.push(
|
||||||
this.attach("search-menu-assistant-item", {
|
this.attach("search-menu-assistant-item", {
|
||||||
slug: `${prefix}${item} `,
|
slug: `${prefix}${item}`,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -430,42 +448,146 @@ createWidget("search-menu-assistant", {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
createWidget("search-menu-initial-options", {
|
||||||
|
tagName: "ul.search-menu-initial-options",
|
||||||
|
|
||||||
|
html(attrs) {
|
||||||
|
if (attrs.term?.match(MODIFIER_REGEXP)) {
|
||||||
|
return this.defaultRow(attrs.term);
|
||||||
|
}
|
||||||
|
|
||||||
|
const service = this.register.lookup("search-service:main");
|
||||||
|
const ctx = service.get("searchContext");
|
||||||
|
|
||||||
|
const content = [];
|
||||||
|
if (attrs.term) {
|
||||||
|
if (ctx) {
|
||||||
|
const term = attrs.term ? `${attrs.term} ` : "";
|
||||||
|
|
||||||
|
switch (ctx.type) {
|
||||||
|
case "topic":
|
||||||
|
content.push(
|
||||||
|
this.attach("search-menu-assistant-item", {
|
||||||
|
slug: `${term}topic:${ctx.id}`,
|
||||||
|
label: [
|
||||||
|
h("span", term),
|
||||||
|
h("span.label-suffix", I18n.t("search.in_this_topic")),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "private_messages":
|
||||||
|
content.push(
|
||||||
|
this.attach("search-menu-assistant-item", {
|
||||||
|
slug: `${term}in:personal`,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case "category":
|
||||||
|
const fullSlug = ctx.category.parentCategory
|
||||||
|
? `#${ctx.category.parentCategory.slug}:${ctx.category.slug}`
|
||||||
|
: `#${ctx.category.slug}`;
|
||||||
|
|
||||||
|
content.push(
|
||||||
|
this.attach("search-menu-assistant", {
|
||||||
|
term: `${term}${fullSlug}`,
|
||||||
|
suggestionKeyword: "#",
|
||||||
|
results: [{ model: ctx.category }],
|
||||||
|
withInLabel: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case "tag":
|
||||||
|
content.push(
|
||||||
|
this.attach("search-menu-assistant", {
|
||||||
|
term: `${term}#${ctx.name}`,
|
||||||
|
suggestionKeyword: "#",
|
||||||
|
results: [{ name: ctx.name }],
|
||||||
|
withInLabel: true,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case "user":
|
||||||
|
content.push(
|
||||||
|
this.attach("search-menu-assistant-item", {
|
||||||
|
slug: `${term}@${ctx.user.username}`,
|
||||||
|
label: [
|
||||||
|
h("span", term),
|
||||||
|
h(
|
||||||
|
"span.label-suffix",
|
||||||
|
I18n.t("search.in_posts_by", {
|
||||||
|
username: ctx.user.username,
|
||||||
|
})
|
||||||
|
),
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const rowOptions = { withLabel: true };
|
||||||
|
content.push(this.defaultRow(attrs.term, rowOptions));
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (content.length === 0) {
|
||||||
|
content.push(this.attach("random-quick-tip"));
|
||||||
|
}
|
||||||
|
|
||||||
|
return content;
|
||||||
|
},
|
||||||
|
|
||||||
|
defaultRow(term, opts = { withLabel: false }) {
|
||||||
|
return this.attach("search-menu-assistant-item", {
|
||||||
|
slug: term,
|
||||||
|
label: [
|
||||||
|
h("span", `${term} `),
|
||||||
|
h(
|
||||||
|
"span.label-suffix",
|
||||||
|
opts.withLabel ? I18n.t("search.in_topics_posts") : null
|
||||||
|
),
|
||||||
|
],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
createWidget("search-menu-assistant-item", {
|
createWidget("search-menu-assistant-item", {
|
||||||
tagName: "li.search-menu-assistant-item",
|
tagName: "li.search-menu-assistant-item",
|
||||||
|
|
||||||
html(attrs) {
|
html(attrs) {
|
||||||
const prefix = attrs.prefix?.trim();
|
const prefix = attrs.prefix?.trim();
|
||||||
|
const attributes = {};
|
||||||
|
attributes.href = "#";
|
||||||
|
|
||||||
|
let content = [iconNode("search")];
|
||||||
|
|
||||||
|
if (prefix) {
|
||||||
|
content.push(h("span.search-item-prefix", `${prefix} `));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attrs.withInLabel) {
|
||||||
|
content.push(h("span.label-suffix", `${I18n.t("search.in")} `));
|
||||||
|
}
|
||||||
|
|
||||||
if (attrs.category) {
|
if (attrs.category) {
|
||||||
return h(
|
attributes.href = attrs.category.url;
|
||||||
"a.widget-link.search-link",
|
|
||||||
{
|
content.push(
|
||||||
attributes: {
|
this.attach("category-link", {
|
||||||
href: attrs.category.url,
|
category: attrs.category,
|
||||||
},
|
allowUncategorized: true,
|
||||||
},
|
recursive: true,
|
||||||
[
|
})
|
||||||
h("span.search-item-prefix", prefix),
|
|
||||||
this.attach("category-link", {
|
|
||||||
category: attrs.category,
|
|
||||||
allowUncategorized: true,
|
|
||||||
recursive: true,
|
|
||||||
}),
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
} else if (attrs.tag) {
|
} else if (attrs.tag) {
|
||||||
return h(
|
attributes.href = getURL(`/tag/${attrs.tag}`);
|
||||||
"a.widget-link.search-link",
|
|
||||||
{
|
content.push(iconNode("tag"));
|
||||||
attributes: {
|
content.push(h("span.search-item-tag", attrs.tag));
|
||||||
href: getURL(`/tag/${attrs.tag}`),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[
|
|
||||||
h("span.search-item-prefix", prefix),
|
|
||||||
iconNode("tag"),
|
|
||||||
h("span.search-item-tag", attrs.tag),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
} else if (attrs.user) {
|
} else if (attrs.user) {
|
||||||
const userResult = [
|
const userResult = [
|
||||||
avatarImg("small", {
|
avatarImg("small", {
|
||||||
|
@ -474,30 +596,11 @@ createWidget("search-menu-assistant-item", {
|
||||||
}),
|
}),
|
||||||
h("span.username", formatUsername(attrs.user.username)),
|
h("span.username", formatUsername(attrs.user.username)),
|
||||||
];
|
];
|
||||||
|
content.push(h("span.search-item-user", userResult));
|
||||||
return h(
|
|
||||||
"a.widget-link.search-link",
|
|
||||||
{
|
|
||||||
attributes: {
|
|
||||||
href: "#",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
[
|
|
||||||
h("span.search-item-prefix", prefix),
|
|
||||||
h("span.search-item-user", userResult),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
return h(
|
content.push(h("span.search-item-slug", attrs.label || attrs.slug));
|
||||||
"a.widget-link.search-link",
|
|
||||||
{
|
|
||||||
attributes: {
|
|
||||||
href: "#",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
h("span.search-item-slug", attrs.slug)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
return h("a.widget-link.search-link", { attributes }, content);
|
||||||
},
|
},
|
||||||
|
|
||||||
click(e) {
|
click(e) {
|
||||||
|
@ -509,3 +612,15 @@ createWidget("search-menu-assistant-item", {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
createWidget("random-quick-tip", {
|
||||||
|
tagName: "li.search-random-quick-tip",
|
||||||
|
|
||||||
|
html() {
|
||||||
|
const item = QUICK_TIPS[Math.floor(Math.random() * QUICK_TIPS.length)];
|
||||||
|
return [
|
||||||
|
h("span.tip-label", item.label),
|
||||||
|
h("span.tip-description", item.description),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
@ -2,9 +2,11 @@ import { isValidSearchTerm, searchForTerm } from "discourse/lib/search";
|
||||||
import DiscourseURL from "discourse/lib/url";
|
import DiscourseURL from "discourse/lib/url";
|
||||||
import { createWidget } from "discourse/widgets/widget";
|
import { createWidget } from "discourse/widgets/widget";
|
||||||
import discourseDebounce from "discourse-common/lib/debounce";
|
import discourseDebounce from "discourse-common/lib/debounce";
|
||||||
import { get } from "@ember/object";
|
|
||||||
import getURL from "discourse-common/lib/get-url";
|
import getURL from "discourse-common/lib/get-url";
|
||||||
import { h } from "virtual-dom";
|
import { h } from "virtual-dom";
|
||||||
|
import I18n from "I18n";
|
||||||
|
import { iconNode } from "discourse-common/lib/icon-library";
|
||||||
|
import { isiPad } from "discourse/lib/utilities";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
import { Promise } from "rsvp";
|
import { Promise } from "rsvp";
|
||||||
import { search as searchCategoryTag } from "discourse/lib/category-tag-search";
|
import { search as searchCategoryTag } from "discourse/lib/category-tag-search";
|
||||||
|
@ -14,6 +16,9 @@ import { CANCELLED_STATUS } from "discourse/lib/autocomplete";
|
||||||
const CATEGORY_SLUG_REGEXP = /(\#[a-zA-Z0-9\-:]*)$/gi;
|
const CATEGORY_SLUG_REGEXP = /(\#[a-zA-Z0-9\-:]*)$/gi;
|
||||||
const USERNAME_REGEXP = /(\@[a-zA-Z0-9\-\_]*)$/gi;
|
const USERNAME_REGEXP = /(\@[a-zA-Z0-9\-\_]*)$/gi;
|
||||||
const SUGGESTIONS_REGEXP = /(in:|status:|order:|:)([a-zA-Z]*)$/gi;
|
const SUGGESTIONS_REGEXP = /(in:|status:|order:|:)([a-zA-Z]*)$/gi;
|
||||||
|
export const TOPIC_REPLACE_REGEXP = /\stopic:\d+/i;
|
||||||
|
export const MODIFIER_REGEXP = /.*(\#|\@|:).*$/gi;
|
||||||
|
export const DEFAULT_TYPE_FILTER = "exclude_topics";
|
||||||
|
|
||||||
const searchData = {};
|
const searchData = {};
|
||||||
|
|
||||||
|
@ -22,10 +27,8 @@ export function initSearchData() {
|
||||||
searchData.results = {};
|
searchData.results = {};
|
||||||
searchData.noResults = false;
|
searchData.noResults = false;
|
||||||
searchData.term = undefined;
|
searchData.term = undefined;
|
||||||
searchData.typeFilter = null;
|
searchData.typeFilter = DEFAULT_TYPE_FILTER;
|
||||||
searchData.invalidTerm = false;
|
searchData.invalidTerm = false;
|
||||||
searchData.topicId = null;
|
|
||||||
searchData.afterAutocomplete = false;
|
|
||||||
searchData.suggestionResults = [];
|
searchData.suggestionResults = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,8 +49,7 @@ const SearchHelper = {
|
||||||
perform(widget) {
|
perform(widget) {
|
||||||
this.cancel();
|
this.cancel();
|
||||||
|
|
||||||
const { term, typeFilter, contextEnabled } = searchData;
|
const { term, typeFilter } = searchData;
|
||||||
const searchContext = contextEnabled ? widget.searchContext() : null;
|
|
||||||
const fullSearchUrl = widget.fullSearchUrl();
|
const fullSearchUrl = widget.fullSearchUrl();
|
||||||
const matchSuggestions = this.matchesSuggestions();
|
const matchSuggestions = this.matchesSuggestions();
|
||||||
|
|
||||||
|
@ -105,7 +107,14 @@ const SearchHelper = {
|
||||||
|
|
||||||
searchData.suggestionKeyword = false;
|
searchData.suggestionKeyword = false;
|
||||||
|
|
||||||
if (!isValidSearchTerm(term, widget.siteSettings)) {
|
if (!term) {
|
||||||
|
searchData.noResults = false;
|
||||||
|
searchData.results = [];
|
||||||
|
searchData.loading = false;
|
||||||
|
searchData.invalidTerm = false;
|
||||||
|
|
||||||
|
widget.scheduleRerender();
|
||||||
|
} else if (!isValidSearchTerm(term, widget.siteSettings)) {
|
||||||
searchData.noResults = true;
|
searchData.noResults = true;
|
||||||
searchData.results = [];
|
searchData.results = [];
|
||||||
searchData.loading = false;
|
searchData.loading = false;
|
||||||
|
@ -114,9 +123,9 @@ const SearchHelper = {
|
||||||
widget.scheduleRerender();
|
widget.scheduleRerender();
|
||||||
} else {
|
} else {
|
||||||
searchData.invalidTerm = false;
|
searchData.invalidTerm = false;
|
||||||
|
|
||||||
this._activeSearch = searchForTerm(term, {
|
this._activeSearch = searchForTerm(term, {
|
||||||
typeFilter,
|
typeFilter,
|
||||||
searchContext,
|
|
||||||
fullSearchUrl,
|
fullSearchUrl,
|
||||||
});
|
});
|
||||||
this._activeSearch
|
this._activeSearch
|
||||||
|
@ -124,49 +133,50 @@ const SearchHelper = {
|
||||||
// we ensure the current search term is the one used
|
// we ensure the current search term is the one used
|
||||||
// when starting the query
|
// when starting the query
|
||||||
if (results && term === searchData.term) {
|
if (results && term === searchData.term) {
|
||||||
|
if (term.includes("topic:")) {
|
||||||
|
widget.appEvents.trigger("post-stream:refresh", { force: true });
|
||||||
|
}
|
||||||
|
|
||||||
searchData.noResults = results.resultTypes.length === 0;
|
searchData.noResults = results.resultTypes.length === 0;
|
||||||
searchData.results = results;
|
searchData.results = results;
|
||||||
|
|
||||||
if (searchContext && searchContext.type === "topic") {
|
|
||||||
widget.appEvents.trigger("post-stream:refresh", { force: true });
|
|
||||||
searchData.topicId = searchContext.id;
|
|
||||||
} else {
|
|
||||||
searchData.topicId = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(popupAjaxError)
|
.catch(popupAjaxError)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
searchData.loading = false;
|
searchData.loading = false;
|
||||||
searchData.afterAutocomplete = false;
|
|
||||||
widget.scheduleRerender();
|
widget.scheduleRerender();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
matchesSuggestions() {
|
matchesSuggestions() {
|
||||||
if (searchData.term === undefined) {
|
if (searchData.term === undefined || this.includesTopics()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const categoriesMatch = searchData.term.match(CATEGORY_SLUG_REGEXP);
|
const term = searchData.term.trim();
|
||||||
|
const categoriesMatch = term.match(CATEGORY_SLUG_REGEXP);
|
||||||
|
|
||||||
if (categoriesMatch) {
|
if (categoriesMatch) {
|
||||||
return { type: "category", categoriesMatch };
|
return { type: "category", categoriesMatch };
|
||||||
}
|
}
|
||||||
|
|
||||||
const usernamesMatch = searchData.term.match(USERNAME_REGEXP);
|
const usernamesMatch = term.match(USERNAME_REGEXP);
|
||||||
if (usernamesMatch) {
|
if (usernamesMatch) {
|
||||||
return { type: "username", usernamesMatch };
|
return { type: "username", usernamesMatch };
|
||||||
}
|
}
|
||||||
|
|
||||||
const suggestionsMatch = searchData.term.match(SUGGESTIONS_REGEXP);
|
const suggestionsMatch = term.match(SUGGESTIONS_REGEXP);
|
||||||
if (suggestionsMatch) {
|
if (suggestionsMatch) {
|
||||||
return suggestionsMatch;
|
return suggestionsMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
includesTopics() {
|
||||||
|
return searchData.typeFilter !== DEFAULT_TYPE_FILTER;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default createWidget("search-menu", {
|
export default createWidget("search-menu", {
|
||||||
|
@ -174,11 +184,6 @@ export default createWidget("search-menu", {
|
||||||
searchData,
|
searchData,
|
||||||
|
|
||||||
fullSearchUrl(opts) {
|
fullSearchUrl(opts) {
|
||||||
const contextEnabled = searchData.contextEnabled;
|
|
||||||
|
|
||||||
const ctx = contextEnabled ? this.searchContext() : null;
|
|
||||||
const type = ctx ? get(ctx, "type") : null;
|
|
||||||
|
|
||||||
let url = "/search";
|
let url = "/search";
|
||||||
const params = [];
|
const params = [];
|
||||||
|
|
||||||
|
@ -187,24 +192,6 @@ export default createWidget("search-menu", {
|
||||||
|
|
||||||
query += `q=${encodeURIComponent(searchData.term)}`;
|
query += `q=${encodeURIComponent(searchData.term)}`;
|
||||||
|
|
||||||
if (contextEnabled && ctx) {
|
|
||||||
if (type === "private_messages") {
|
|
||||||
if (
|
|
||||||
this.currentUser &&
|
|
||||||
ctx.id.toString().toLowerCase() ===
|
|
||||||
this.currentUser.get("username_lower")
|
|
||||||
) {
|
|
||||||
query += " in:personal";
|
|
||||||
} else {
|
|
||||||
query += encodeURIComponent(
|
|
||||||
` personal_messages:${ctx.id.toString().toLowerCase()}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
query += encodeURIComponent(" " + type + ":" + ctx.id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (query) {
|
if (query) {
|
||||||
params.push(query);
|
params.push(query);
|
||||||
}
|
}
|
||||||
|
@ -222,37 +209,47 @@ export default createWidget("search-menu", {
|
||||||
},
|
},
|
||||||
|
|
||||||
panelContents() {
|
panelContents() {
|
||||||
const { contextEnabled, afterAutocomplete } = searchData;
|
let searchInput = [this.attach("search-term", { value: searchData.term })];
|
||||||
|
if (searchData.loading) {
|
||||||
let searchInput = [
|
|
||||||
this.attach(
|
|
||||||
"search-term",
|
|
||||||
{ value: searchData.term, contextEnabled },
|
|
||||||
{ state: { afterAutocomplete } }
|
|
||||||
),
|
|
||||||
];
|
|
||||||
if (searchData.term && searchData.loading) {
|
|
||||||
searchInput.push(h("div.searching", h("div.spinner")));
|
searchInput.push(h("div.searching", h("div.spinner")));
|
||||||
|
} else {
|
||||||
|
const clearButton = this.attach("link", {
|
||||||
|
attributes: {
|
||||||
|
title: I18n.t("search.clear_search"),
|
||||||
|
},
|
||||||
|
action: "clearSearch",
|
||||||
|
className: "clear-search",
|
||||||
|
contents: () => iconNode("times"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const advancedSearchButton = this.attach("link", {
|
||||||
|
href: this.fullSearchUrl({ expanded: true }),
|
||||||
|
contents: () => iconNode("sliders-h"),
|
||||||
|
className: "show-advanced-search",
|
||||||
|
title: I18n.t("search.open_advanced"),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (searchData.term) {
|
||||||
|
searchInput.push(
|
||||||
|
h("div.searching", [clearButton, advancedSearchButton])
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
searchInput.push(h("div.searching", advancedSearchButton));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = [
|
const results = [h("div.search-input", searchInput)];
|
||||||
h("div.search-input", searchInput),
|
|
||||||
this.attach("search-context", {
|
|
||||||
contextEnabled,
|
|
||||||
url: this.fullSearchUrl({ expanded: true }),
|
|
||||||
}),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (searchData.term && !searchData.loading) {
|
if (!searchData.loading) {
|
||||||
results.push(
|
results.push(
|
||||||
this.attach("search-menu-results", {
|
this.attach("search-menu-results", {
|
||||||
term: searchData.term,
|
term: searchData.term,
|
||||||
noResults: searchData.noResults,
|
noResults: searchData.noResults,
|
||||||
results: searchData.results,
|
results: searchData.results,
|
||||||
invalidTerm: searchData.invalidTerm,
|
invalidTerm: searchData.invalidTerm,
|
||||||
searchContextEnabled: searchData.contextEnabled,
|
|
||||||
suggestionKeyword: searchData.suggestionKeyword,
|
suggestionKeyword: searchData.suggestionKeyword,
|
||||||
suggestionResults: searchData.suggestionResults,
|
suggestionResults: searchData.suggestionResults,
|
||||||
|
searchTopics: SearchHelper.includesTopics(),
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -260,6 +257,14 @@ export default createWidget("search-menu", {
|
||||||
return results;
|
return results;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
clearSearch() {
|
||||||
|
searchData.term = "";
|
||||||
|
const searchInput = document.getElementById("search-term");
|
||||||
|
searchInput.value = "";
|
||||||
|
searchInput.focus();
|
||||||
|
this.triggerSearch();
|
||||||
|
},
|
||||||
|
|
||||||
searchService() {
|
searchService() {
|
||||||
if (!this._searchService) {
|
if (!this._searchService) {
|
||||||
this._searchService = this.register.lookup("search-service:main");
|
this._searchService = this.register.lookup("search-service:main");
|
||||||
|
@ -267,29 +272,7 @@ export default createWidget("search-menu", {
|
||||||
return this._searchService;
|
return this._searchService;
|
||||||
},
|
},
|
||||||
|
|
||||||
searchContext() {
|
html() {
|
||||||
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", {
|
return this.attach("menu-panel", {
|
||||||
maxWidth: 500,
|
maxWidth: 500,
|
||||||
contents: () => this.panelContents(),
|
contents: () => this.panelContents(),
|
||||||
|
@ -312,18 +295,21 @@ export default createWidget("search-menu", {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (e.which === 65 /* a */) {
|
if (e.which === 65 /* a */) {
|
||||||
let focused = $("header .results .search-link:focus");
|
if (document.activeElement?.classList.contains("search-link")) {
|
||||||
if (focused.length === 1) {
|
if (document.querySelector("#reply-control.open")) {
|
||||||
if ($("#reply-control.open").length === 1) {
|
|
||||||
// add a link and focus composer
|
// add a link and focus composer
|
||||||
|
|
||||||
this.appEvents.trigger("composer:insert-text", focused[0].href, {
|
this.appEvents.trigger(
|
||||||
ensureSpace: true,
|
"composer:insert-text",
|
||||||
});
|
document.activeElement.getAttribute("href"),
|
||||||
|
{
|
||||||
|
ensureSpace: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
this.appEvents.trigger("header:keyboard-trigger", { type: "search" });
|
this.appEvents.trigger("header:keyboard-trigger", { type: "search" });
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
$("#reply-control.open textarea").focus();
|
document.querySelector("#reply-control.open textarea").focus();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -332,20 +318,28 @@ export default createWidget("search-menu", {
|
||||||
const up = e.which === 38;
|
const up = e.which === 38;
|
||||||
const down = e.which === 40;
|
const down = e.which === 40;
|
||||||
if (up || down) {
|
if (up || down) {
|
||||||
let focused = $(".search-menu *:focus")[0];
|
let focused = document.activeElement.closest(".search-menu")
|
||||||
|
? document.activeElement
|
||||||
|
: null;
|
||||||
|
|
||||||
if (!focused) {
|
if (!focused) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let links = $(".search-menu .results a");
|
let links = document.querySelectorAll(".search-menu .results a");
|
||||||
let results = $(".search-menu .results .search-link");
|
let results = document.querySelectorAll(
|
||||||
|
".search-menu .results .search-link"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!results.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let prevResult;
|
let prevResult;
|
||||||
let result;
|
let result;
|
||||||
|
|
||||||
links.each((idx, item) => {
|
links.forEach((item) => {
|
||||||
if ($(item).hasClass("search-link")) {
|
if (item.classList.contains("search-link")) {
|
||||||
prevResult = item;
|
prevResult = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,30 +351,46 @@ export default createWidget("search-menu", {
|
||||||
let index = -1;
|
let index = -1;
|
||||||
|
|
||||||
if (result) {
|
if (result) {
|
||||||
index = results.index(result);
|
index = Array.prototype.indexOf.call(results, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index === -1 && down) {
|
if (index === -1 && down) {
|
||||||
$(".search-menu .search-link:first").focus();
|
document.querySelector(".search-menu .results .search-link").focus();
|
||||||
} else if (index === 0 && up) {
|
} else if (index === 0 && up) {
|
||||||
$(".search-menu input:first").focus();
|
document.querySelector(".search-menu input#search-term").focus();
|
||||||
} else if (index > -1) {
|
} else if (index > -1) {
|
||||||
index += down ? 1 : -1;
|
index += down ? 1 : -1;
|
||||||
if (index >= 0 && index < results.length) {
|
if (index >= 0 && index < results.length) {
|
||||||
$(results[index]).focus();
|
results[index].focus();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const searchInput = document.querySelector("#search-term");
|
||||||
|
if (e.which === 13 && e.target === searchInput) {
|
||||||
|
// same combination as key-enter-escape mixin
|
||||||
|
if (e.ctrlKey || e.metaKey || (isiPad() && e.altKey)) {
|
||||||
|
this.fullSearch();
|
||||||
|
} else {
|
||||||
|
searchData.typeFilter = null;
|
||||||
|
this.triggerSearch();
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
triggerSearch() {
|
triggerSearch() {
|
||||||
searchData.noResults = false;
|
searchData.noResults = false;
|
||||||
this.searchService().set("highlightTerm", searchData.term);
|
if (searchData.term.includes("topic:")) {
|
||||||
searchData.loading = true;
|
const highlightTerm = searchData.term.replace(TOPIC_REPLACE_REGEXP, "");
|
||||||
discourseDebounce(SearchHelper, SearchHelper.perform, this, 400);
|
this.searchService().set("highlightTerm", highlightTerm);
|
||||||
|
}
|
||||||
|
searchData.loading = SearchHelper.includesTopics() ? true : false;
|
||||||
|
|
||||||
|
const delay = SearchHelper.includesTopics() ? 400 : 200;
|
||||||
|
discourseDebounce(SearchHelper, SearchHelper.perform, this, delay);
|
||||||
},
|
},
|
||||||
|
|
||||||
moreOfType(type) {
|
moreOfType(type) {
|
||||||
|
@ -388,30 +398,17 @@ export default createWidget("search-menu", {
|
||||||
this.triggerSearch();
|
this.triggerSearch();
|
||||||
},
|
},
|
||||||
|
|
||||||
searchContextChanged(enabled) {
|
searchTermChanged(term, opts = {}) {
|
||||||
// This indicates the checkbox has been clicked, NOT that the context has changed.
|
searchData.typeFilter = opts.searchTopics ? null : DEFAULT_TYPE_FILTER;
|
||||||
searchData.typeFilter = null;
|
|
||||||
this.sendWidgetAction("searchMenuContextChanged", enabled);
|
|
||||||
searchData.contextEnabled = enabled;
|
|
||||||
this.triggerSearch();
|
|
||||||
},
|
|
||||||
|
|
||||||
searchTermChanged(term) {
|
|
||||||
searchData.typeFilter = null;
|
|
||||||
searchData.term = term;
|
searchData.term = term;
|
||||||
this.triggerSearch();
|
this.triggerSearch();
|
||||||
},
|
},
|
||||||
|
|
||||||
triggerAutocomplete(term) {
|
triggerAutocomplete(term) {
|
||||||
searchData.afterAutocomplete = true;
|
this.searchTermChanged(term, { searchTopics: true });
|
||||||
this.searchTermChanged(term);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
fullSearch() {
|
fullSearch() {
|
||||||
if (!isValidSearchTerm(searchData.term, this.siteSettings)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
searchData.results = [];
|
searchData.results = [];
|
||||||
searchData.loading = false;
|
searchData.loading = false;
|
||||||
SearchHelper.cancel();
|
SearchHelper.cancel();
|
||||||
|
|
|
@ -245,9 +245,14 @@ acceptance("Group - Authenticated", function (needs) {
|
||||||
);
|
);
|
||||||
|
|
||||||
await click("#search-button");
|
await click("#search-button");
|
||||||
assert.ok(
|
await fillIn("#search-term", "smth");
|
||||||
exists(".search-context input:checked"),
|
|
||||||
"scope to message checkbox is checked"
|
assert.equal(
|
||||||
|
query(
|
||||||
|
".search-menu .results .search-menu-assistant-item:first-child"
|
||||||
|
).innerText.trim(),
|
||||||
|
"smth in:personal",
|
||||||
|
"contextual search is available as first option"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -3,22 +3,28 @@ import {
|
||||||
count,
|
count,
|
||||||
exists,
|
exists,
|
||||||
query,
|
query,
|
||||||
queryAll,
|
|
||||||
} from "discourse/tests/helpers/qunit-helpers";
|
} from "discourse/tests/helpers/qunit-helpers";
|
||||||
import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers";
|
import { click, fillIn, triggerKeyEvent, visit } from "@ember/test-helpers";
|
||||||
|
import I18n from "I18n";
|
||||||
import searchFixtures from "discourse/tests/fixtures/search-fixtures";
|
import searchFixtures from "discourse/tests/fixtures/search-fixtures";
|
||||||
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
import selectKit from "discourse/tests/helpers/select-kit-helper";
|
||||||
import { test } from "qunit";
|
import { test } from "qunit";
|
||||||
|
import { DEFAULT_TYPE_FILTER } from "discourse/widgets/search-menu";
|
||||||
|
|
||||||
acceptance("Search - Anonymous", function (needs) {
|
acceptance("Search - Anonymous", function (needs) {
|
||||||
let calledEmpty = false;
|
|
||||||
|
|
||||||
needs.pretender((server, helper) => {
|
needs.pretender((server, helper) => {
|
||||||
server.get("/search/query", (request) => {
|
server.get("/search/query", (request) => {
|
||||||
if (!request.queryParams["search_context[type]"]) {
|
if (request.queryParams.type_filter === DEFAULT_TYPE_FILTER) {
|
||||||
calledEmpty = true;
|
// posts/topics are not present in the payload by default
|
||||||
|
return helper.response({
|
||||||
|
users: searchFixtures["search/query"]["users"],
|
||||||
|
categories: searchFixtures["search/query"]["categories"],
|
||||||
|
tags: searchFixtures["search/query"]["tags"],
|
||||||
|
groups: searchFixtures["search/query"]["groups"],
|
||||||
|
grouped_search_result:
|
||||||
|
searchFixtures["search/query"]["grouped_search_result"],
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return helper.response(searchFixtures["search/query"]);
|
return helper.response(searchFixtures["search/query"]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -28,24 +34,61 @@ acceptance("Search - Anonymous", function (needs) {
|
||||||
|
|
||||||
await click("#search-button");
|
await click("#search-button");
|
||||||
|
|
||||||
assert.ok(exists("#search-term"), "it shows the search bar");
|
assert.ok(exists("#search-term"), "it shows the search input");
|
||||||
assert.ok(!exists(".search-menu .results ul li"), "no results by default");
|
assert.ok(
|
||||||
|
exists(".show-advanced-search"),
|
||||||
|
"it shows full page search button"
|
||||||
|
);
|
||||||
|
assert.ok(
|
||||||
|
exists(".search-menu .results ul li.search-random-quick-tip"),
|
||||||
|
"shows random quick tip by default"
|
||||||
|
);
|
||||||
|
|
||||||
await fillIn("#search-term", "dev");
|
await fillIn("#search-term", "dev");
|
||||||
await triggerKeyEvent("#search-term", "keyup", 16);
|
|
||||||
assert.ok(exists(".search-menu .results ul li"), "it shows results");
|
assert.ok(
|
||||||
|
!exists(".search-menu .results ul li.search-random-quick-tip"),
|
||||||
|
"quick tip no longer shown"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
query(
|
||||||
|
".search-menu .results ul.search-menu-initial-options li:first-child"
|
||||||
|
).innerText.trim(),
|
||||||
|
`dev ${I18n.t("search.in_topics_posts")}`,
|
||||||
|
"shows topic search as first dropdown item"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
exists(".search-menu .search-result-category ul li"),
|
||||||
|
"shows matching category results"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
exists(".search-menu .search-result-user ul li"),
|
||||||
|
"shows matching user results"
|
||||||
|
);
|
||||||
|
|
||||||
|
await triggerKeyEvent(".search-menu", "keydown", 40);
|
||||||
|
await click(document.activeElement);
|
||||||
|
|
||||||
|
assert.ok(
|
||||||
|
exists(".search-menu .search-result-topic ul li"),
|
||||||
|
"shows topic results"
|
||||||
|
);
|
||||||
assert.ok(
|
assert.ok(
|
||||||
exists(".search-menu .results ul li .topic-title[data-topic-id]"),
|
exists(".search-menu .results ul li .topic-title[data-topic-id]"),
|
||||||
"topic has data-topic-id"
|
"topic has data-topic-id"
|
||||||
);
|
);
|
||||||
|
|
||||||
await click(".show-help");
|
await click(".show-advanced-search");
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
queryAll(".full-page-search").val(),
|
query(".full-page-search").value,
|
||||||
"dev",
|
"dev",
|
||||||
"it shows the search term"
|
"it goes to full search page and preserves the search term"
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.ok(
|
assert.ok(
|
||||||
exists(".search-advanced-options"),
|
exists(".search-advanced-options"),
|
||||||
"advanced search is expanded"
|
"advanced search is expanded"
|
||||||
|
@ -68,85 +111,75 @@ acceptance("Search - Anonymous", function (needs) {
|
||||||
assert.ok(!exists(".search-menu"));
|
assert.ok(!exists(".search-menu"));
|
||||||
});
|
});
|
||||||
|
|
||||||
test("search for a tag", async function (assert) {
|
test("search scope", async function (assert) {
|
||||||
await visit("/");
|
const firstResult =
|
||||||
|
".search-menu .results .search-menu-assistant-item:first-child";
|
||||||
|
|
||||||
await click("#search-button");
|
|
||||||
|
|
||||||
await fillIn("#search-term", "evil");
|
|
||||||
await triggerKeyEvent("#search-term", "keyup", 16);
|
|
||||||
assert.ok(exists(".search-menu .results ul li"), "it shows results");
|
|
||||||
});
|
|
||||||
|
|
||||||
test("search scope checkbox", async function (assert) {
|
|
||||||
await visit("/tag/important");
|
await visit("/tag/important");
|
||||||
await click("#search-button");
|
await click("#search-button");
|
||||||
assert.ok(
|
await fillIn("#search-term", "smth");
|
||||||
exists(".search-context input:checked"),
|
|
||||||
"scope to tag checkbox is checked"
|
assert.equal(
|
||||||
|
query(firstResult).textContent.trim(),
|
||||||
|
`smth ${I18n.t("search.in")} test`,
|
||||||
|
"tag-scoped search is first available option"
|
||||||
);
|
);
|
||||||
await click("#search-button");
|
|
||||||
|
|
||||||
await visit("/c/bug");
|
await visit("/c/bug");
|
||||||
await click("#search-button");
|
await click("#search-button");
|
||||||
assert.ok(
|
|
||||||
exists(".search-context input:checked"),
|
assert.equal(
|
||||||
"scope to category checkbox is checked"
|
query(firstResult).textContent.trim(),
|
||||||
|
`smth ${I18n.t("search.in")} bug`,
|
||||||
|
"category-scoped search is first available option"
|
||||||
);
|
);
|
||||||
await click("#search-button");
|
|
||||||
|
|
||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
await click("#search-button");
|
await click("#search-button");
|
||||||
assert.not(
|
|
||||||
exists(".search-context input:checked"),
|
assert.equal(
|
||||||
"scope to topic checkbox is not checked"
|
query(firstResult).textContent.trim(),
|
||||||
|
`smth ${I18n.t("search.in_this_topic")}`,
|
||||||
|
"topic-scoped search is first available option"
|
||||||
);
|
);
|
||||||
await click("#search-button");
|
|
||||||
|
|
||||||
await visit("/u/eviltrout");
|
await visit("/u/eviltrout");
|
||||||
await click("#search-button");
|
await click("#search-button");
|
||||||
assert.ok(
|
|
||||||
exists(".search-context input:checked"),
|
assert.equal(
|
||||||
"scope to user checkbox is checked"
|
query(firstResult).textContent.trim(),
|
||||||
|
`smth ${I18n.t("search.in_posts_by", {
|
||||||
|
username: "eviltrout",
|
||||||
|
})}`,
|
||||||
|
"user-scoped search is first available option"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Search with context", async function (assert) {
|
test("search scope for topics", async function (assert) {
|
||||||
await visit("/t/internationalization-localization/280/1");
|
await visit("/t/internationalization-localization/280/1");
|
||||||
|
|
||||||
await click("#search-button");
|
await click("#search-button");
|
||||||
await fillIn("#search-term", "a proper");
|
await fillIn("#search-term", "a proper");
|
||||||
await click(".search-context input[type='checkbox']");
|
await focus("input#search-term");
|
||||||
await triggerKeyEvent("#search-term", "keyup", 16);
|
await triggerKeyEvent(".search-menu", "keydown", 40);
|
||||||
|
|
||||||
assert.ok(exists(".search-menu .results ul li"), "it shows results");
|
await click(document.activeElement);
|
||||||
|
assert.ok(
|
||||||
const highlighted = [];
|
exists(".search-menu .search-result-post ul li"),
|
||||||
|
"clicking first option formats results as posts"
|
||||||
queryAll("#post_7 span.highlighted").map((_, span) => {
|
|
||||||
highlighted.push(span.innerText);
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.deepEqual(
|
|
||||||
highlighted,
|
|
||||||
["a proper"],
|
|
||||||
"it should highlight the post with the search terms correctly"
|
|
||||||
);
|
);
|
||||||
|
|
||||||
calledEmpty = false;
|
assert.equal(
|
||||||
await visit("/");
|
query("#post_7 span.highlighted").textContent.trim(),
|
||||||
await click("#search-button");
|
"a proper",
|
||||||
|
"highlights the post correctly"
|
||||||
|
);
|
||||||
|
|
||||||
assert.ok(!exists(".search-context input[type='checkbox']"));
|
await click(".clear-search");
|
||||||
assert.ok(calledEmpty, "it triggers a new search");
|
assert.equal(query("#search-term").value, "", "clear button works");
|
||||||
|
|
||||||
await visit("/t/internationalization-localization/280/1");
|
|
||||||
await click("#search-button");
|
|
||||||
|
|
||||||
assert.ok(!$(".search-context input[type=checkbox]").is(":checked"));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Right filters are shown to anonymous users", async function (assert) {
|
test("Right filters are shown in full page search", async function (assert) {
|
||||||
const inSelector = selectKit(".select-kit#in");
|
const inSelector = selectKit(".select-kit#in");
|
||||||
|
|
||||||
await visit("/search?expanded=true");
|
await visit("/search?expanded=true");
|
||||||
|
@ -205,7 +238,7 @@ acceptance("Search - Authenticated", function (needs) {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test("Right filters are shown to logged-in users", async function (assert) {
|
test("Right filters are shown in full page search", async function (assert) {
|
||||||
const inSelector = selectKit(".select-kit#in");
|
const inSelector = selectKit(".select-kit#in");
|
||||||
|
|
||||||
await visit("/search?expanded=true");
|
await visit("/search?expanded=true");
|
||||||
|
@ -230,16 +263,96 @@ acceptance("Search - Authenticated", function (needs) {
|
||||||
|
|
||||||
test("Works with empty result sets", async function (assert) {
|
test("Works with empty result sets", async function (assert) {
|
||||||
await visit("/t/internationalization-localization/280");
|
await visit("/t/internationalization-localization/280");
|
||||||
await click(".search-dropdown");
|
await click("#search-button");
|
||||||
await click(".search-context input[type=checkbox]");
|
|
||||||
await fillIn("#search-term", "plans");
|
await fillIn("#search-term", "plans");
|
||||||
await triggerKeyEvent("#search-term", "keyup", 32);
|
await focus("input#search-term");
|
||||||
assert.notEqual(count(".item"), 0);
|
await triggerKeyEvent(".search-menu", "keydown", 40);
|
||||||
|
await click(document.activeElement);
|
||||||
|
|
||||||
|
assert.notEqual(count(".search-menu .results .item"), 0);
|
||||||
|
|
||||||
await fillIn("#search-term", "plans empty");
|
await fillIn("#search-term", "plans empty");
|
||||||
await triggerKeyEvent("#search-term", "keyup", 32);
|
await triggerKeyEvent("#search-term", "keydown", 13);
|
||||||
assert.equal(count(".item"), 0);
|
|
||||||
assert.equal(count(".no-results"), 1);
|
assert.equal(count(".search-menu .results .item"), 0);
|
||||||
|
assert.equal(count(".search-menu .results .no-results"), 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("search dropdown keyboard navigation", async function (assert) {
|
||||||
|
const keyEnter = 13;
|
||||||
|
const keyArrowDown = 40;
|
||||||
|
const keyArrowUp = 38;
|
||||||
|
const keyEsc = 27;
|
||||||
|
const keyA = 65;
|
||||||
|
const container = ".search-menu .results";
|
||||||
|
|
||||||
|
await visit("/");
|
||||||
|
await click("#search-button");
|
||||||
|
await fillIn("#search-term", "dev");
|
||||||
|
|
||||||
|
assert.ok(exists(query(`${container} ul li`)), "has a list of items");
|
||||||
|
|
||||||
|
await triggerKeyEvent("#search-term", "keydown", keyEnter);
|
||||||
|
assert.ok(
|
||||||
|
exists(query(`${container} .search-result-topic`)),
|
||||||
|
"has topic results"
|
||||||
|
);
|
||||||
|
|
||||||
|
await triggerKeyEvent("#search-term", "keydown", keyArrowDown);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
document.activeElement.getAttribute("href"),
|
||||||
|
query(`${container} li:first-child a`).getAttribute("href"),
|
||||||
|
"arrow down selects first element"
|
||||||
|
);
|
||||||
|
|
||||||
|
await triggerKeyEvent("#search-term", "keydown", keyArrowDown);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
document.activeElement.getAttribute("href"),
|
||||||
|
query(`${container} li:nth-child(2) a`).getAttribute("href"),
|
||||||
|
"arrow down selects next element"
|
||||||
|
);
|
||||||
|
|
||||||
|
await triggerKeyEvent("#search-term", "keydown", keyArrowDown);
|
||||||
|
await triggerKeyEvent("#search-term", "keydown", keyArrowDown);
|
||||||
|
await triggerKeyEvent("#search-term", "keydown", keyArrowDown);
|
||||||
|
await triggerKeyEvent("#search-term", "keydown", keyArrowDown);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
document.activeElement.getAttribute("href"),
|
||||||
|
"/search?q=dev",
|
||||||
|
"arrow down sets focus to more results link"
|
||||||
|
);
|
||||||
|
|
||||||
|
await triggerKeyEvent(".search-menu", "keydown", keyEsc);
|
||||||
|
assert.ok(!exists(".search-menu:visible"), "Esc removes search dropdown");
|
||||||
|
|
||||||
|
await click("#search-button");
|
||||||
|
await triggerKeyEvent(".search-menu", "keydown", keyArrowDown);
|
||||||
|
await triggerKeyEvent(".search-menu", "keydown", keyArrowUp);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
document.activeElement.tagName.toLowerCase(),
|
||||||
|
"input",
|
||||||
|
"arrow up sets focus to search term input"
|
||||||
|
);
|
||||||
|
|
||||||
|
await triggerKeyEvent(".search-menu", "keydown", keyEsc);
|
||||||
|
await click("#create-topic");
|
||||||
|
await click("#search-button");
|
||||||
|
await triggerKeyEvent(".search-menu", "keydown", keyArrowDown);
|
||||||
|
|
||||||
|
const firstLink = query(`${container} li:nth-child(1) a`).getAttribute(
|
||||||
|
"href"
|
||||||
|
);
|
||||||
|
await triggerKeyEvent(".search-menu", "keydown", keyA);
|
||||||
|
|
||||||
|
assert.equal(
|
||||||
|
query("#reply-control textarea").value,
|
||||||
|
firstLink,
|
||||||
|
"hitting A when focused on a search result copies link to composer"
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -249,19 +362,17 @@ acceptance("Search - with tagging enabled", function (needs) {
|
||||||
|
|
||||||
test("displays tags", async function (assert) {
|
test("displays tags", async function (assert) {
|
||||||
await visit("/");
|
await visit("/");
|
||||||
|
|
||||||
await click("#search-button");
|
await click("#search-button");
|
||||||
|
|
||||||
await fillIn("#search-term", "dev");
|
await fillIn("#search-term", "dev");
|
||||||
await triggerKeyEvent("#search-term", "keyup", 16);
|
await triggerKeyEvent("#search-term", "keydown", 13);
|
||||||
|
|
||||||
const tags = queryAll(
|
assert.equal(
|
||||||
".search-menu .results ul li:nth-of-type(1) .discourse-tags"
|
query(
|
||||||
)
|
".search-menu .results ul li:nth-of-type(1) .discourse-tags"
|
||||||
.text()
|
).textContent.trim(),
|
||||||
.trim();
|
"dev slow",
|
||||||
|
"tags displayed in search results"
|
||||||
assert.equal(tags, "dev slow");
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("displays tag shortcuts", async function (assert) {
|
test("displays tag shortcuts", async function (assert) {
|
||||||
|
@ -276,7 +387,7 @@ acceptance("Search - with tagging enabled", function (needs) {
|
||||||
".search-menu .results ul.search-menu-assistant .search-link";
|
".search-menu .results ul.search-menu-assistant .search-link";
|
||||||
assert.ok(exists(query(firstItem)));
|
assert.ok(exists(query(firstItem)));
|
||||||
|
|
||||||
const firstTag = query(`${firstItem} .search-item-tag`).innerText.trim();
|
const firstTag = query(`${firstItem} .search-item-tag`).textContent.trim();
|
||||||
assert.equal(firstTag, "monkey");
|
assert.equal(firstTag, "monkey");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -325,10 +436,10 @@ acceptance("Search - assistant", function (needs) {
|
||||||
|
|
||||||
const firstResultSlug = query(
|
const firstResultSlug = query(
|
||||||
`${firstCategory} .category-name`
|
`${firstCategory} .category-name`
|
||||||
).innerText.trim();
|
).textContent.trim();
|
||||||
|
|
||||||
await click(firstCategory);
|
await click(firstCategory);
|
||||||
assert.equal(query("#search-term").value, `#${firstResultSlug} `);
|
assert.equal(query("#search-term").value, `#${firstResultSlug}`);
|
||||||
|
|
||||||
await fillIn("#search-term", "sam #");
|
await fillIn("#search-term", "sam #");
|
||||||
await triggerKeyEvent("#search-term", "keyup", 51);
|
await triggerKeyEvent("#search-term", "keyup", 51);
|
||||||
|
@ -338,11 +449,11 @@ acceptance("Search - assistant", function (needs) {
|
||||||
query(
|
query(
|
||||||
".search-menu .results ul.search-menu-assistant .search-item-prefix"
|
".search-menu .results ul.search-menu-assistant .search-item-prefix"
|
||||||
).innerText,
|
).innerText,
|
||||||
"sam"
|
"sam "
|
||||||
);
|
);
|
||||||
|
|
||||||
await click(firstCategory);
|
await click(firstCategory);
|
||||||
assert.equal(query("#search-term").value, `sam #${firstResultSlug} `);
|
assert.equal(query("#search-term").value, `sam #${firstResultSlug}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
test("shows in: shortcuts", async function (assert) {
|
test("shows in: shortcuts", async function (assert) {
|
||||||
|
@ -379,6 +490,6 @@ acceptance("Search - assistant", function (needs) {
|
||||||
assert.equal(firstUsername, "TeaMoe");
|
assert.equal(firstUsername, "TeaMoe");
|
||||||
|
|
||||||
await click(query(firstUser));
|
await click(query(firstUser));
|
||||||
assert.equal(query("#search-term").value, `@${firstUsername} `);
|
assert.equal(query("#search-term").value, `@${firstUsername}`);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,22 +1,27 @@
|
||||||
|
@mixin user-item-flex {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
line-height: $line-height-medium;
|
||||||
|
color: var(--primary-high-or-secondary-low);
|
||||||
|
}
|
||||||
|
|
||||||
|
@mixin separator {
|
||||||
|
border-top: 1px solid var(--primary-low);
|
||||||
|
margin-top: 0.5em;
|
||||||
|
padding-top: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
$search-pad-vertical: 0.25em;
|
||||||
|
$search-pad-horizontal: 0.5em;
|
||||||
|
|
||||||
.search-menu {
|
.search-menu {
|
||||||
--search-padding: 0.5em;
|
|
||||||
.menu-panel .panel-body-contents {
|
.menu-panel .panel-body-contents {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-input {
|
.search-input {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: var(--search-padding);
|
padding: $search-pad-vertical 0.1em;
|
||||||
}
|
|
||||||
|
|
||||||
.search-context {
|
|
||||||
label {
|
|
||||||
padding: 0.25em var(--search-padding);
|
|
||||||
}
|
|
||||||
.show-help {
|
|
||||||
margin-left: auto;
|
|
||||||
line-height: var(--line-height-medium);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.heading {
|
.heading {
|
||||||
|
@ -27,28 +32,15 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
input[type="text"] {
|
input[type="text"] {
|
||||||
box-sizing: border-box;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: 32px;
|
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-context {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
label {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-context + .results {
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.results {
|
.results {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: column;
|
||||||
|
padding-top: $search-pad-vertical;
|
||||||
|
padding-bottom: $search-pad-vertical;
|
||||||
|
|
||||||
.list {
|
.list {
|
||||||
min-width: 100px;
|
min-width: 100px;
|
||||||
|
@ -79,11 +71,16 @@
|
||||||
.second-line {
|
.second-line {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
align-items: center;
|
align-items: baseline;
|
||||||
|
|
||||||
.discourse-tags {
|
.badge-wrapper {
|
||||||
.discourse-tag {
|
margin-right: 0.5em;
|
||||||
margin-right: 0.25em;
|
}
|
||||||
|
.discourse-tags .discourse-tag {
|
||||||
|
margin-right: 0.25em;
|
||||||
|
|
||||||
|
.d-icon {
|
||||||
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,215 +92,186 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-results {
|
.search-result-category {
|
||||||
|
.widget-link {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-group .group-result,
|
||||||
|
.search-result-user .user-result {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex: 1 1 auto;
|
align-items: center;
|
||||||
.topic-statuses {
|
font-size: var(--font-down-1);
|
||||||
color: var(--primary-medium);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
.search-result-group .group-result {
|
||||||
.main-results + .secondary-results {
|
.d-icon,
|
||||||
border-left: 1px solid var(--primary-low);
|
.avatar-flair {
|
||||||
margin-left: 1em;
|
width: 20px;
|
||||||
padding-left: 1em;
|
height: 20px;
|
||||||
max-width: 33%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.secondary-results {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex: 1 1 auto;
|
|
||||||
|
|
||||||
.separator {
|
|
||||||
margin: 1em 0.25em;
|
|
||||||
height: 1px;
|
|
||||||
background: var(--primary-low);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-result-tag {
|
.avatar-flair {
|
||||||
.discourse-tag {
|
|
||||||
font-size: $font-down-1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-result-category {
|
|
||||||
.widget-link {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-result-group {
|
|
||||||
.search-link {
|
|
||||||
color: var(--primary-high);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
color: var(--primary);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-result {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.d-icon,
|
|
||||||
.avatar-flair {
|
|
||||||
min-width: 25px;
|
|
||||||
margin-right: 0.5em;
|
|
||||||
|
|
||||||
.d-icon {
|
|
||||||
margin-right: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.avatar-flair-image {
|
|
||||||
background-repeat: no-repeat;
|
|
||||||
background-size: 100% 100%;
|
|
||||||
min-height: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.group-names {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: auto;
|
|
||||||
line-height: $line-height-medium;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
.name,
|
|
||||||
.slug {
|
|
||||||
color: var(--primary-high);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.name,
|
|
||||||
.slug {
|
|
||||||
@include ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.slug {
|
|
||||||
font-size: $font-down-1;
|
|
||||||
color: var(--primary-high);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-result-category,
|
|
||||||
.search-result-user,
|
|
||||||
.search-result-group,
|
|
||||||
.search-result-tag {
|
|
||||||
.list {
|
|
||||||
display: block;
|
|
||||||
|
|
||||||
.item {
|
|
||||||
.widget-link.search-link {
|
|
||||||
flex: 1;
|
|
||||||
font-size: $font-0;
|
|
||||||
padding: 0.25em;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-result-user {
|
|
||||||
.user-result {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
|
||||||
|
|
||||||
.avatar {
|
|
||||||
margin-right: 0.5em;
|
|
||||||
display: block;
|
|
||||||
min-width: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.user-titles {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
overflow: auto;
|
|
||||||
line-height: $line-height-medium;
|
|
||||||
|
|
||||||
.username,
|
|
||||||
.name {
|
|
||||||
@include ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.username {
|
|
||||||
color: var(--primary-high-or-secondary-low);
|
|
||||||
font-size: $font-down-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.custom-field {
|
|
||||||
color: var(--primary-high-or-secondary-low);
|
|
||||||
font-size: $font-down-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.name {
|
|
||||||
color: var(--primary-high-or-secondary-low);
|
|
||||||
font-size: $font-0;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-menu-assistant {
|
|
||||||
min-width: 100%;
|
|
||||||
margin-top: -1em;
|
|
||||||
|
|
||||||
.search-menu-assistant-item {
|
|
||||||
> span {
|
|
||||||
vertical-align: baseline;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.search-item-user .avatar,
|
|
||||||
.search-item-prefix {
|
|
||||||
margin-right: 0.5em;
|
margin-right: 0.5em;
|
||||||
|
border-radius: 50%;
|
||||||
|
&.avatar-flair-image {
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: 100% 100%;
|
||||||
|
}
|
||||||
|
.d-icon {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-item-tag {
|
.group-names {
|
||||||
color: var(--primary-high);
|
@include user-item-flex;
|
||||||
font-size: var(--font-down-1);
|
.name,
|
||||||
}
|
.slug {
|
||||||
|
@include ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
.d-icon-tag {
|
.name {
|
||||||
// match category badge styling
|
font-weight: 700;
|
||||||
// tag/category suggestions can be displayed simultaneously
|
}
|
||||||
font-size: var(--font-down-2);
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-user .user-result {
|
||||||
|
.user-titles {
|
||||||
|
@include user-item-flex;
|
||||||
|
|
||||||
|
.username,
|
||||||
|
.name {
|
||||||
|
@include ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
.username,
|
||||||
|
.name,
|
||||||
|
.custom-field {
|
||||||
|
color: var(--primary-high-or-secondary-low);
|
||||||
|
}
|
||||||
|
|
||||||
|
.custom-field {
|
||||||
|
font-size: var(--font-down-2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-category,
|
||||||
|
.search-result-tag {
|
||||||
|
+ .search-result-user,
|
||||||
|
+ .search-result-group {
|
||||||
|
@include separator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result-user .user-result img.avatar,
|
||||||
|
.search-item-user img.avatar {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.label-suffix {
|
||||||
|
color: var(--primary-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
.badge-wrapper {
|
||||||
|
font-size: var(--font-0);
|
||||||
|
margin-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-menu-initial-options {
|
||||||
|
+ .search-result-tag,
|
||||||
|
+ .search-result-category,
|
||||||
|
+ .search-result-user,
|
||||||
|
+ .search-result-group {
|
||||||
|
@include separator;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-menu-initial-options,
|
||||||
|
.search-result-tag,
|
||||||
|
.search-menu-assistant {
|
||||||
|
.search-link {
|
||||||
|
.d-icon {
|
||||||
|
margin-right: 5px;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.d-icon-tag {
|
||||||
|
font-size: var(--font-down-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.d-icon-search {
|
||||||
|
font-size: var(--font-down-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-random-quick-tip {
|
||||||
|
padding: $search-pad-vertical $search-pad-horizontal;
|
||||||
|
padding-bottom: 0;
|
||||||
|
font-size: var(--font-down-2);
|
||||||
|
color: var(--primary-medium);
|
||||||
|
.tip-label {
|
||||||
|
background-color: rgba(var(--tertiary-rgb), 0.1);
|
||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
display: inline-block;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.searching {
|
.searching {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 1.1em;
|
top: $search-pad-vertical + 0.4em;
|
||||||
right: 1em;
|
right: $search-pad-horizontal;
|
||||||
|
min-height: 20px;
|
||||||
|
|
||||||
.spinner {
|
.spinner {
|
||||||
width: 10px;
|
width: 12px;
|
||||||
height: 10px;
|
height: 12px;
|
||||||
border-width: 2px;
|
border-width: 2px;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.show-advanced-search,
|
||||||
|
a.clear-search {
|
||||||
|
padding: 0px 3px;
|
||||||
|
display: inline-block;
|
||||||
|
background-color: transparent;
|
||||||
|
.d-icon {
|
||||||
|
color: var(--primary-low-mid);
|
||||||
|
}
|
||||||
|
&:hover .d-icon {
|
||||||
|
color: var(--primary-high);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a.clear-search {
|
||||||
|
margin-right: 3px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.no-results {
|
.no-results {
|
||||||
padding: var(--search-padding);
|
padding: $search-pad-vertical $search-pad-horizontal;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-link {
|
.search-link {
|
||||||
padding: var(--search-padding);
|
display: block;
|
||||||
|
padding: $search-pad-vertical $search-pad-horizontal;
|
||||||
|
|
||||||
.badge-category-parent {
|
// This is purposefully redundant
|
||||||
line-height: $line-height-small;
|
// the search widget can be used outside of the header
|
||||||
|
// and the focus/hover styles from the header in those cases wouldn't follow
|
||||||
|
&:focus,
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--highlight-medium);
|
||||||
}
|
}
|
||||||
|
|
||||||
.topic {
|
.topic {
|
||||||
|
@ -319,4 +287,10 @@
|
||||||
margin-right: 0.25em;
|
margin-right: 0.25em;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.search-result-topic,
|
||||||
|
.search-result-post {
|
||||||
|
.search-link {
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2341,11 +2341,13 @@ en:
|
||||||
select_all: "Select All"
|
select_all: "Select All"
|
||||||
clear_all: "Clear All"
|
clear_all: "Clear All"
|
||||||
too_short: "Your search term is too short."
|
too_short: "Your search term is too short."
|
||||||
|
open_advanced: "Open advanced search"
|
||||||
|
clear_search: "Clear search"
|
||||||
sort_or_bulk_actions: "Sort or bulk select results"
|
sort_or_bulk_actions: "Sort or bulk select results"
|
||||||
result_count:
|
result_count:
|
||||||
one: "<span>%{count} result for</span><span class='term'>%{term}</span>"
|
one: "<span>%{count} result for</span><span class='term'>%{term}</span>"
|
||||||
other: "<span>%{count}%{plus} results for</span><span class='term'>%{term}</span>"
|
other: "<span>%{count}%{plus} results for</span><span class='term'>%{term}</span>"
|
||||||
title: "search topics, posts, users, or categories"
|
title: "Search"
|
||||||
full_page_title: "Search"
|
full_page_title: "Search"
|
||||||
no_results: "No results found."
|
no_results: "No results found."
|
||||||
no_more_results: "No more results found."
|
no_more_results: "No more results found."
|
||||||
|
@ -2361,6 +2363,10 @@ en:
|
||||||
search_term_label: "enter search keyword"
|
search_term_label: "enter search keyword"
|
||||||
categories: "Categories"
|
categories: "Categories"
|
||||||
tags: "Tags"
|
tags: "Tags"
|
||||||
|
in: "in"
|
||||||
|
in_this_topic: "in this topic"
|
||||||
|
in_topics_posts: "in all topics and posts"
|
||||||
|
in_posts_by: "in posts by %{username}"
|
||||||
|
|
||||||
type:
|
type:
|
||||||
default: "Topics/posts"
|
default: "Topics/posts"
|
||||||
|
@ -2375,6 +2381,14 @@ en:
|
||||||
topic: "Search this topic"
|
topic: "Search this topic"
|
||||||
private_messages: "Search messages"
|
private_messages: "Search messages"
|
||||||
|
|
||||||
|
tips:
|
||||||
|
category_tag: "filters by category or tag"
|
||||||
|
author: "filters by post author"
|
||||||
|
in: "filters by metadata (e.g. in:title, in:personal, in:pinned)"
|
||||||
|
status: "filters by topic status"
|
||||||
|
full_search: "launches full page search"
|
||||||
|
full_search_key: "%{modifier} + Enter"
|
||||||
|
|
||||||
advanced:
|
advanced:
|
||||||
title: Advanced filters
|
title: Advanced filters
|
||||||
posted_by:
|
posted_by:
|
||||||
|
|
|
@ -33,7 +33,7 @@ class Search
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.facets
|
def self.facets
|
||||||
%w(topic category user private_messages tags all_topics)
|
%w(topic category user private_messages tags all_topics exclude_topics)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.ts_config(locale = SiteSetting.default_locale)
|
def self.ts_config(locale = SiteSetting.default_locale)
|
||||||
|
@ -230,7 +230,7 @@ class Search
|
||||||
end
|
end
|
||||||
|
|
||||||
def limit
|
def limit
|
||||||
if @opts[:type_filter].present?
|
if @opts[:type_filter].present? && @opts[:type_filter] != "exclude_topics"
|
||||||
Search.per_filter + 1
|
Search.per_filter + 1
|
||||||
else
|
else
|
||||||
Search.per_facet + 1
|
Search.per_facet + 1
|
||||||
|
@ -862,13 +862,13 @@ class Search
|
||||||
groups = Group
|
groups = Group
|
||||||
.visible_groups(@guardian.user, "name ASC", include_everyone: false)
|
.visible_groups(@guardian.user, "name ASC", include_everyone: false)
|
||||||
.where("name ILIKE :term OR full_name ILIKE :term", term: "%#{@term}%")
|
.where("name ILIKE :term OR full_name ILIKE :term", term: "%#{@term}%")
|
||||||
|
.limit(limit)
|
||||||
|
|
||||||
groups.each { |group| @results.add(group) }
|
groups.each { |group| @results.add(group) }
|
||||||
end
|
end
|
||||||
|
|
||||||
def tags_search
|
def tags_search
|
||||||
return unless SiteSetting.tagging_enabled
|
return unless SiteSetting.tagging_enabled
|
||||||
|
|
||||||
tags = Tag.includes(:tag_search_data)
|
tags = Tag.includes(:tag_search_data)
|
||||||
.where("tag_search_data.search_data @@ #{ts_query}")
|
.where("tag_search_data.search_data @@ #{ts_query}")
|
||||||
.references(:tag_search_data)
|
.references(:tag_search_data)
|
||||||
|
@ -882,6 +882,15 @@ class Search
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def exclude_topics_search
|
||||||
|
if @term.present?
|
||||||
|
user_search
|
||||||
|
category_search
|
||||||
|
tags_search
|
||||||
|
groups_search
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
PHRASE_MATCH_REGEXP_PATTERN = '"([^"]+)"'
|
PHRASE_MATCH_REGEXP_PATTERN = '"([^"]+)"'
|
||||||
|
|
||||||
def posts_query(limit, type_filter: nil, aggregate_search: false)
|
def posts_query(limit, type_filter: nil, aggregate_search: false)
|
||||||
|
|
|
@ -180,6 +180,7 @@ module SvgSprite
|
||||||
"sign-in-alt",
|
"sign-in-alt",
|
||||||
"sign-out-alt",
|
"sign-out-alt",
|
||||||
"signal",
|
"signal",
|
||||||
|
"sliders-h",
|
||||||
"star",
|
"star",
|
||||||
"step-backward",
|
"step-backward",
|
||||||
"step-forward",
|
"step-forward",
|
||||||
|
|
|
@ -1921,4 +1921,33 @@ describe Search do
|
||||||
expect(Search.new("advanced order:chars").execute.posts).to eq([post0, post1])
|
expect(Search.new("advanced order:chars").execute.posts).to eq([post0, post1])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'exclude_topics filter' do
|
||||||
|
before { SiteSetting.tagging_enabled = true }
|
||||||
|
let!(:user) { Fabricate(:user) }
|
||||||
|
fab!(:group) { Fabricate(:group, name: 'bruce-world-fans') }
|
||||||
|
fab!(:topic) { Fabricate(:topic, title: 'Bruce topic not a result') }
|
||||||
|
|
||||||
|
it 'works' do
|
||||||
|
category = Fabricate(:category_with_definition, name: 'bruceland', user: user)
|
||||||
|
tag = Fabricate(:tag, name: 'brucealicious')
|
||||||
|
|
||||||
|
result = Search.execute('bruce', type_filter: 'exclude_topics')
|
||||||
|
|
||||||
|
expect(result.users.map(&:id)).to contain_exactly(user.id)
|
||||||
|
|
||||||
|
expect(result.categories.map(&:id)).to contain_exactly(category.id)
|
||||||
|
|
||||||
|
expect(result.groups.map(&:id)).to contain_exactly(group.id)
|
||||||
|
|
||||||
|
expect(result.tags.map(&:id)).to contain_exactly(tag.id)
|
||||||
|
|
||||||
|
expect(result.posts.length).to eq(0)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not fail when parsed term is empty' do
|
||||||
|
result = Search.execute('#cat ', type_filter: 'exclude_topics')
|
||||||
|
expect(result.categories.length).to eq(0)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue