mirror of
https://github.com/discourse/discourse.git
synced 2025-03-04 18:29:20 +00:00
DEV: Restructure search menu so that it can be rendered outside of header (#23852)
This commit is contained in:
parent
ef5cb6e7ed
commit
e110256cb0
@ -0,0 +1,7 @@
|
||||
<MenuPanel @animationClass={{this.animationClass}}>
|
||||
<SearchMenu
|
||||
@closeSearchMenu={{@closeSearchMenu}}
|
||||
@inlineResults={{true}}
|
||||
@autofocusInput={{true}}
|
||||
/>
|
||||
</MenuPanel>
|
@ -0,0 +1,11 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default class SearchMenuPanel extends Component {
|
||||
@service site;
|
||||
get animationClass() {
|
||||
return this.site.mobileView || this.site.narrowDesktopView
|
||||
? "slide-in"
|
||||
: "drop-down";
|
||||
}
|
||||
}
|
@ -1,23 +1,83 @@
|
||||
<MenuPanel @animationClass={{this.animationClass}}>
|
||||
<SearchMenu::MenuPanelContents
|
||||
@clearTopicContext={{this.clearTopicContext}}
|
||||
@clearPMInboxContext={{this.clearPMInboxContext}}
|
||||
@inPMInboxContext={{this.inPMInboxContext}}
|
||||
@searchTermChanged={{this.searchTermChanged}}
|
||||
@loading={{this.loading}}
|
||||
@fullSearchUrl={{this.fullSearchUrl}}
|
||||
@fullSearch={{this.fullSearch}}
|
||||
@triggerSearch={{this.triggerSearch}}
|
||||
@clearSearch={{this.clearSearch}}
|
||||
@includesTopics={{this.includesTopics}}
|
||||
@noResults={{this.noResults}}
|
||||
@results={{this.results}}
|
||||
@invalidTerm={{this.invalidTerm}}
|
||||
@suggestionKeyword={{this.suggestionKeyword}}
|
||||
@suggestionResults={{this.suggestionResults}}
|
||||
@searchTopics={{this.includesTopics}}
|
||||
@typeFilter={{this.typeFilter}}
|
||||
@updateTypeFilter={{this.updateTypeFilter}}
|
||||
@closeSearchMenu={{@closeSearchMenu}}
|
||||
/>
|
||||
</MenuPanel>
|
||||
<div class={{this.classNames}} {{did-insert this.setupEventListeners}}>
|
||||
<div class="search-input">
|
||||
{{#if this.search.inTopicContext}}
|
||||
<DButton
|
||||
@icon="times"
|
||||
@label="search.in_this_topic"
|
||||
@title="search.in_this_topic_tooltip"
|
||||
@action={{this.clearTopicContext}}
|
||||
class="btn-small search-context"
|
||||
/>
|
||||
{{else if this.inPMInboxContext}}
|
||||
<DButton
|
||||
@icon="times"
|
||||
@label="search.in_messages"
|
||||
@title="search.in_messages_tooltip"
|
||||
@action={{this.clearPMInboxContext}}
|
||||
class="btn-small search-context"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<SearchMenu::SearchTerm
|
||||
@searchTermChanged={{this.searchTermChanged}}
|
||||
@typeFilter={{this.typeFilter}}
|
||||
@updateTypeFilter={{this.updateTypeFilter}}
|
||||
@triggerSearch={{this.triggerSearch}}
|
||||
@fullSearch={{this.fullSearch}}
|
||||
@clearPMInboxContext={{this.clearPMInboxContext}}
|
||||
@clearTopicContext={{this.clearTopicContext}}
|
||||
@closeSearchMenu={{this.close}}
|
||||
@openSearchMenu={{this.open}}
|
||||
@autofocus={{@autofocusInput}}
|
||||
/>
|
||||
|
||||
{{#if this.loading}}
|
||||
<div class="searching">
|
||||
{{loading-spinner}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="searching">
|
||||
{{#if this.search.activeGlobalSearchTerm}}
|
||||
<SearchMenu::ClearButton @clearSearch={{this.clearSearch}} />
|
||||
{{/if}}
|
||||
<SearchMenu::AdvancedButton @href={{this.advancedSearchButtonHref}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if @inlineResults}}
|
||||
<SearchMenu::Results
|
||||
@loading={{this.loading}}
|
||||
@noResults={{this.noResults}}
|
||||
@results={{this.results}}
|
||||
@invalidTerm={{this.invalidTerm}}
|
||||
@suggestionKeyword={{this.suggestionKeyword}}
|
||||
@suggestionResults={{this.suggestionResults}}
|
||||
@searchTopics={{this.includesTopics}}
|
||||
@inPMInboxContext={{this.inPMInboxContext}}
|
||||
@triggerSearch={{this.triggerSearch}}
|
||||
@updateTypeFilter={{this.updateTypeFilter}}
|
||||
@closeSearchMenu={{this.close}}
|
||||
@searchTermChanged={{this.searchTermChanged}}
|
||||
@clearSearch={{this.clearSearch}}
|
||||
/>
|
||||
{{else if this.displayMenuPanelResults}}
|
||||
<MenuPanel>
|
||||
<SearchMenu::Results
|
||||
@loading={{this.loading}}
|
||||
@noResults={{this.noResults}}
|
||||
@results={{this.results}}
|
||||
@invalidTerm={{this.invalidTerm}}
|
||||
@suggestionKeyword={{this.suggestionKeyword}}
|
||||
@suggestionResults={{this.suggestionResults}}
|
||||
@searchTopics={{this.includesTopics}}
|
||||
@inPMInboxContext={{this.inPMInboxContext}}
|
||||
@triggerSearch={{this.triggerSearch}}
|
||||
@updateTypeFilter={{this.updateTypeFilter}}
|
||||
@closeSearchMenu={{this.close}}
|
||||
@searchTermChanged={{this.searchTermChanged}}
|
||||
@clearSearch={{this.clearSearch}}
|
||||
/>
|
||||
</MenuPanel>
|
||||
{{/if}}
|
||||
</div>
|
@ -22,7 +22,6 @@ const CATEGORY_SLUG_REGEXP = /(\#[a-zA-Z0-9\-:]*)$/gi;
|
||||
const USERNAME_REGEXP = /(\@[a-zA-Z0-9\-\_]*)$/gi;
|
||||
const SUGGESTIONS_REGEXP = /(in:|status:|order:|:)([a-zA-Z]*)$/gi;
|
||||
export const SEARCH_INPUT_ID = "search-term";
|
||||
export const SEARCH_BUTTON_ID = "search-button";
|
||||
export const MODIFIER_REGEXP = /.*(\#|\@|:).*$/gi;
|
||||
export const DEFAULT_TYPE_FILTER = "exclude_topics";
|
||||
|
||||
@ -30,16 +29,11 @@ export function focusSearchInput() {
|
||||
document.getElementById(SEARCH_INPUT_ID).focus();
|
||||
}
|
||||
|
||||
export function focusSearchButton() {
|
||||
document.getElementById(SEARCH_BUTTON_ID).focus();
|
||||
}
|
||||
|
||||
export default class SearchMenu extends Component {
|
||||
@service search;
|
||||
@service currentUser;
|
||||
@service siteSettings;
|
||||
@service appEvents;
|
||||
@service site;
|
||||
|
||||
@tracked loading = false;
|
||||
@tracked results = {};
|
||||
@ -50,13 +44,42 @@ export default class SearchMenu extends Component {
|
||||
@tracked suggestionKeyword = false;
|
||||
@tracked suggestionResults = [];
|
||||
@tracked invalidTerm = false;
|
||||
@tracked menuPanelOpen = false;
|
||||
|
||||
_debouncer = null;
|
||||
_activeSearch = null;
|
||||
|
||||
get animationClass() {
|
||||
return this.site.mobileView || this.site.narrowDesktopView
|
||||
? "slide-in"
|
||||
: "drop-down";
|
||||
@bind
|
||||
setupEventListeners() {
|
||||
document.addEventListener("mousedown", this.onDocumentPress, true);
|
||||
document.addEventListener("touchend", this.onDocumentPress, {
|
||||
capture: true,
|
||||
passive: true,
|
||||
});
|
||||
}
|
||||
|
||||
willDestroy() {
|
||||
document.removeEventListener("mousedown", this.onDocumentPress);
|
||||
document.removeEventListener("touchend", this.onDocumentPress);
|
||||
|
||||
super.willDestroy(...arguments);
|
||||
}
|
||||
|
||||
@bind
|
||||
onDocumentPress(event) {
|
||||
if (!event.target.closest(".search-menu-container.menu-panel-results")) {
|
||||
this.menuPanelOpen = false;
|
||||
}
|
||||
}
|
||||
|
||||
get classNames() {
|
||||
const classes = ["search-menu-container"];
|
||||
|
||||
if (!this.args.inlineResults) {
|
||||
classes.push("menu-panel-results");
|
||||
}
|
||||
|
||||
return classes.join(" ");
|
||||
}
|
||||
|
||||
get includesTopics() {
|
||||
@ -71,6 +94,23 @@ export default class SearchMenu extends Component {
|
||||
return false;
|
||||
}
|
||||
|
||||
@action
|
||||
close() {
|
||||
if (this.args?.closeSearchMenu) {
|
||||
return this.args.closeSearchMenu();
|
||||
}
|
||||
|
||||
// We want to blur the active element (search input) when in stand-alone mode
|
||||
// so that when we focus on the search input again, the menu panel pops up
|
||||
document.activeElement.blur();
|
||||
this.menuPanelOpen = false;
|
||||
}
|
||||
|
||||
@action
|
||||
open() {
|
||||
this.menuPanelOpen = true;
|
||||
}
|
||||
|
||||
@bind
|
||||
fullSearchUrl(opts) {
|
||||
let url = "/search";
|
||||
@ -95,6 +135,18 @@ export default class SearchMenu extends Component {
|
||||
return getURL(url);
|
||||
}
|
||||
|
||||
get advancedSearchButtonHref() {
|
||||
return this.fullSearchUrl({ expanded: true });
|
||||
}
|
||||
|
||||
get displayMenuPanelResults() {
|
||||
if (this.args.inlineResults) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.menuPanelOpen;
|
||||
}
|
||||
|
||||
@bind
|
||||
clearSearch(e) {
|
||||
e.stopPropagation();
|
||||
|
@ -1,63 +0,0 @@
|
||||
<div class="search-input">
|
||||
{{#if this.search.inTopicContext}}
|
||||
<DButton
|
||||
@icon="times"
|
||||
@label="search.in_this_topic"
|
||||
@title="search.in_this_topic_tooltip"
|
||||
@action={{@clearTopicContext}}
|
||||
class="btn-small search-context"
|
||||
/>
|
||||
{{else if @inPMInboxContext}}
|
||||
<DButton
|
||||
@icon="times"
|
||||
@label="search.in_messages"
|
||||
@title="search.in_messages_tooltip"
|
||||
@action={{@clearPMInboxContext}}
|
||||
class="btn-small search-context"
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
<SearchMenu::SearchTerm
|
||||
@searchTermChanged={{@searchTermChanged}}
|
||||
@typeFilter={{@typeFilter}}
|
||||
@updateTypeFilter={{@updateTypeFilter}}
|
||||
@triggerSearch={{@triggerSearch}}
|
||||
@fullSearch={{@fullSearch}}
|
||||
@clearPMInboxContext={{@clearPMInboxContext}}
|
||||
@clearTopicContext={{@clearTopicContext}}
|
||||
@closeSearchMenu={{@closeSearchMenu}}
|
||||
/>
|
||||
|
||||
{{#if @loading}}
|
||||
<div class="searching">
|
||||
{{loading-spinner}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="searching">
|
||||
{{#if this.search.activeGlobalSearchTerm}}
|
||||
<SearchMenu::ClearButton @clearSearch={{@clearSearch}} />
|
||||
{{/if}}
|
||||
<SearchMenu::AdvancedButton @href={{this.advancedSearchButtonHref}} />
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if (and this.search.inTopicContext (not @includesTopics))}}
|
||||
<SearchMenu::BrowserSearchTip />
|
||||
{{else}}
|
||||
{{#unless @loading}}
|
||||
<SearchMenu::Results
|
||||
@noResults={{@noResults}}
|
||||
@results={{@results}}
|
||||
@invalidTerm={{@invalidTerm}}
|
||||
@suggestionKeyword={{@suggestionKeyword}}
|
||||
@suggestionResults={{@suggestionResults}}
|
||||
@searchTopics={{@includesTopics}}
|
||||
@inPMInboxContext={{@inPMInboxContext}}
|
||||
@triggerSearch={{@triggerSearch}}
|
||||
@updateTypeFilter={{@updateTypeFilter}}
|
||||
@closeSearchMenu={{@closeSearchMenu}}
|
||||
@searchTermChanged={{@searchTermChanged}}
|
||||
/>
|
||||
{{/unless}}
|
||||
{{/if}}
|
@ -1,10 +0,0 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { inject as service } from "@ember/service";
|
||||
|
||||
export default class MenuPanelContents extends Component {
|
||||
@service search;
|
||||
|
||||
get advancedSearchButtonHref() {
|
||||
return this.args.fullSearchUrl({ expanded: true });
|
||||
}
|
||||
}
|
@ -1,52 +1,56 @@
|
||||
<div class="results">
|
||||
{{#if @suggestionKeyword}}
|
||||
<SearchMenu::Results::Assistant
|
||||
@suggestionKeyword={{@suggestionKeyword}}
|
||||
@results={{@suggestionResults}}
|
||||
@closeSearchMenu={{@closeSearchMenu}}
|
||||
@searchTermChanged={{@searchTermChanged}}
|
||||
/>
|
||||
{{else if this.termTooShort}}
|
||||
<div class="no-results">{{i18n "search.too_short"}}</div>
|
||||
{{else if this.noTopicResults}}
|
||||
<div class="no-results">{{i18n "search.no_results"}}</div>
|
||||
{{else if this.renderInitialOptions}}
|
||||
<SearchMenu::Results::InitialOptions
|
||||
@closeSearchMenu={{@closeSearchMenu}}
|
||||
@searchTermChanged={{@searchTermChanged}}
|
||||
/>
|
||||
{{else}}
|
||||
{{#if @searchTopics}}
|
||||
{{! render results after a search has been performed }}
|
||||
{{#if this.resultTypesWithComponent}}
|
||||
<SearchMenu::Results::Types
|
||||
@resultTypes={{this.resultTypesWithComponent}}
|
||||
@topicResultsOnly={{true}}
|
||||
@closeSearchMenu={{@closeSearchMenu}}
|
||||
/>
|
||||
<SearchMenu::Results::MoreLink
|
||||
@updateTypeFilter={{@updateTypeFilter}}
|
||||
@triggerSearch={{@triggerSearch}}
|
||||
@resultTypes={{this.resultTypesWithComponent}}
|
||||
@closeSearchMenu={{@closeSearchMenu}}
|
||||
@searchTermChanged={{@searchTermChanged}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{#if (and this.search.inTopicContext (not @searchTopics))}}
|
||||
<SearchMenu::BrowserSearchTip />
|
||||
{{else if (not @loading)}}
|
||||
<div class="results">
|
||||
{{#if @suggestionKeyword}}
|
||||
<SearchMenu::Results::Assistant
|
||||
@suggestionKeyword={{@suggestionKeyword}}
|
||||
@results={{@suggestionResults}}
|
||||
@closeSearchMenu={{@closeSearchMenu}}
|
||||
@searchTermChanged={{@searchTermChanged}}
|
||||
/>
|
||||
{{else if this.termTooShort}}
|
||||
<div class="no-results">{{i18n "search.too_short"}}</div>
|
||||
{{else if this.noTopicResults}}
|
||||
<div class="no-results">{{i18n "search.no_results"}}</div>
|
||||
{{else if this.renderInitialOptions}}
|
||||
<SearchMenu::Results::InitialOptions
|
||||
@closeSearchMenu={{@closeSearchMenu}}
|
||||
@searchTermChanged={{@searchTermChanged}}
|
||||
/>
|
||||
{{else}}
|
||||
{{#unless @inPMInboxContext}}
|
||||
{{! render the first couple suggestions before a search has been performed}}
|
||||
<SearchMenu::Results::InitialOptions
|
||||
@closeSearchMenu={{@closeSearchMenu}}
|
||||
@searchTermChanged={{@searchTermChanged}}
|
||||
/>
|
||||
{{#if @searchTopics}}
|
||||
{{! render results after a search has been performed }}
|
||||
{{#if this.resultTypesWithComponent}}
|
||||
<SearchMenu::Results::Types
|
||||
@resultTypes={{this.resultTypesWithComponent}}
|
||||
@topicResultsOnly={{true}}
|
||||
@closeSearchMenu={{@closeSearchMenu}}
|
||||
/>
|
||||
<SearchMenu::Results::MoreLink
|
||||
@updateTypeFilter={{@updateTypeFilter}}
|
||||
@triggerSearch={{@triggerSearch}}
|
||||
@resultTypes={{this.resultTypesWithComponent}}
|
||||
@closeSearchMenu={{@closeSearchMenu}}
|
||||
@searchTermChanged={{@searchTermChanged}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
{{else}}
|
||||
{{#unless @inPMInboxContext}}
|
||||
{{! render the first couple suggestions before a search has been performed}}
|
||||
<SearchMenu::Results::InitialOptions
|
||||
@closeSearchMenu={{@closeSearchMenu}}
|
||||
@searchTermChanged={{@searchTermChanged}}
|
||||
/>
|
||||
{{#if this.resultTypesWithComponent}}
|
||||
<SearchMenu::Results::Types
|
||||
@resultTypes={{this.resultTypesWithComponent}}
|
||||
@closeSearchMenu={{@closeSearchMenu}}
|
||||
@searchTermChanged={{@searchTermChanged}}
|
||||
/>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
@ -3,10 +3,7 @@ import getURL from "discourse-common/lib/get-url";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { action } from "@ember/object";
|
||||
import { debounce } from "discourse-common/utils/decorators";
|
||||
import {
|
||||
focusSearchButton,
|
||||
focusSearchInput,
|
||||
} from "discourse/components/search-menu";
|
||||
import { focusSearchInput } from "discourse/components/search-menu";
|
||||
|
||||
export default class AssistantItem extends Component {
|
||||
@service search;
|
||||
@ -65,7 +62,6 @@ export default class AssistantItem extends Component {
|
||||
}
|
||||
|
||||
if (e.key === "Escape") {
|
||||
focusSearchButton();
|
||||
this.args.closeSearchMenu();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
|
@ -2,7 +2,11 @@
|
||||
{{! template-lint-disable no-invalid-interactive }}
|
||||
<div class="search-menu__show-more" {{on "keyup" this.onKeyup}}>
|
||||
{{#if this.moreUrl}}
|
||||
<a href={{this.moreUrl}} class="filter search-link">
|
||||
<a
|
||||
href={{this.moreUrl}}
|
||||
{{on "click" this.transitionToMoreUrl}}
|
||||
class="filter search-link"
|
||||
>
|
||||
{{i18n "more"}}...
|
||||
</a>
|
||||
{{else if this.topicResults.more}}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Component from "@glimmer/component";
|
||||
import DiscourseURL from "discourse/lib/url";
|
||||
import { action } from "@ember/object";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { focusSearchButton } from "discourse/components/search-menu";
|
||||
|
||||
export default class MoreLink extends Component {
|
||||
@service search;
|
||||
@ -17,16 +17,24 @@ export default class MoreLink extends Component {
|
||||
return this.topicResults.moreUrl && this.topicResults.moreUrl();
|
||||
}
|
||||
|
||||
@action
|
||||
transitionToMoreUrl(event) {
|
||||
event.preventDefault();
|
||||
this.args.closeSearchMenu();
|
||||
DiscourseURL.routeTo(this.moreUrl);
|
||||
return false;
|
||||
}
|
||||
|
||||
@action
|
||||
moreOfType(type) {
|
||||
this.args.updateTypeFilter(type);
|
||||
this.args.triggerSearch();
|
||||
this.args.closeSearchMenu();
|
||||
}
|
||||
|
||||
@action
|
||||
onKeyup(e) {
|
||||
if (e.key === "Escape") {
|
||||
focusSearchButton();
|
||||
this.args.closeSearchMenu();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
|
@ -2,7 +2,6 @@ import Component from "@glimmer/component";
|
||||
import { inject as service } from "@ember/service";
|
||||
import User from "discourse/models/user";
|
||||
import { action } from "@ember/object";
|
||||
import { focusSearchButton } from "discourse/components/search-menu";
|
||||
|
||||
export default class RecentSearches extends Component {
|
||||
@service currentUser;
|
||||
@ -32,7 +31,6 @@ export default class RecentSearches extends Component {
|
||||
@action
|
||||
onKeyup(e) {
|
||||
if (e.key === "Escape") {
|
||||
focusSearchButton();
|
||||
this.args.closeSearchMenu();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
|
@ -8,7 +8,11 @@
|
||||
{{! template-lint-disable no-pointer-down-event-binding }}
|
||||
{{! template-lint-disable no-invalid-interactive }}
|
||||
<li class="item" {{on "keydown" this.onKeydown}}>
|
||||
<a href={{or result.url result.path}} class="search-link">
|
||||
<a
|
||||
href={{or result.url result.path}}
|
||||
{{on "click" this.onClick}}
|
||||
class="search-link"
|
||||
>
|
||||
<resultType.component @result={{result}} />
|
||||
</a>
|
||||
</li>
|
||||
|
@ -1,7 +1,6 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { action } from "@ember/object";
|
||||
import { focusSearchButton } from "discourse/components/search-menu";
|
||||
|
||||
export default class Types extends Component {
|
||||
@service search;
|
||||
@ -20,10 +19,14 @@ export default class Types extends Component {
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
onClick() {
|
||||
this.args.closeSearchMenu();
|
||||
}
|
||||
|
||||
@action
|
||||
onKeydown(e) {
|
||||
if (e.key === "Escape") {
|
||||
focusSearchButton();
|
||||
this.args.closeSearchMenu();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
|
@ -7,5 +7,6 @@
|
||||
aria-label={{i18n "search.title"}}
|
||||
{{on "keyup" this.onKeyup}}
|
||||
{{on "input" this.updateSearchTerm}}
|
||||
{{on "focus" @openSearchMenu}}
|
||||
{{did-insert this.focus}}
|
||||
/>
|
@ -6,7 +6,6 @@ import { inject as service } from "@ember/service";
|
||||
import {
|
||||
DEFAULT_TYPE_FILTER,
|
||||
SEARCH_INPUT_ID,
|
||||
focusSearchButton,
|
||||
} from "discourse/components/search-menu";
|
||||
|
||||
const SECOND_ENTER_MAX_DELAY = 15000;
|
||||
@ -35,19 +34,22 @@ export default class SearchTerm extends Component {
|
||||
|
||||
@action
|
||||
focus(element) {
|
||||
element.focus();
|
||||
element.select();
|
||||
if (this.args.autofocus) {
|
||||
element.focus();
|
||||
element.select();
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
onKeyup(e) {
|
||||
if (e.key === "Escape") {
|
||||
focusSearchButton();
|
||||
this.args.closeSearchMenu();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
this.args.openSearchMenu();
|
||||
|
||||
this.search.handleArrowUpOrDown(e);
|
||||
|
||||
if (e.key === "Enter") {
|
||||
|
@ -12,7 +12,8 @@ import { wantsNewWindow } from "discourse/lib/intercept-click";
|
||||
import { logSearchLinkClick } from "discourse/lib/search";
|
||||
import RenderGlimmer from "discourse/widgets/render-glimmer";
|
||||
import { hbs } from "ember-cli-htmlbars";
|
||||
import { SEARCH_BUTTON_ID } from "discourse/components/search-menu";
|
||||
|
||||
const SEARCH_BUTTON_ID = "search-button";
|
||||
|
||||
let _extraHeaderIcons = [];
|
||||
|
||||
@ -393,14 +394,17 @@ createWidget("glimmer-search-menu-wrapper", {
|
||||
new RenderGlimmer(
|
||||
this,
|
||||
"div.widget-component-connector",
|
||||
hbs`<SearchMenu @closeSearchMenu={{@data.closeSearchMenu}} />`,
|
||||
{ closeSearchMenu: this.closeSearchMenu.bind(this) }
|
||||
hbs`<SearchMenuPanel @closeSearchMenu={{@data.closeSearchMenu}} />`,
|
||||
{
|
||||
closeSearchMenu: this.closeSearchMenu.bind(this),
|
||||
}
|
||||
),
|
||||
];
|
||||
},
|
||||
|
||||
closeSearchMenu() {
|
||||
this.sendWidgetAction("toggleSearchMenu");
|
||||
document.getElementById(SEARCH_BUTTON_ID)?.focus();
|
||||
},
|
||||
|
||||
clickOutside() {
|
||||
|
@ -0,0 +1,58 @@
|
||||
import I18n from "I18n";
|
||||
import SearchMenu from "discourse/components/search-menu";
|
||||
import { module, test } from "qunit";
|
||||
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||
import { click, fillIn, render, triggerKeyEvent } from "@ember/test-helpers";
|
||||
import { exists, query } from "discourse/tests/helpers/qunit-helpers";
|
||||
|
||||
// Note this isn't a full-fledge test of the search menu. Those tests are in
|
||||
// acceptance/glimmer-search-test.js. This is simply about the rendering of the
|
||||
// menu panel separate from the search input.
|
||||
module("Integration | Component | search-menu", function (hooks) {
|
||||
setupRenderingTest(hooks);
|
||||
|
||||
test("rendering standalone", async function (assert) {
|
||||
await render(<template><SearchMenu /></template>);
|
||||
|
||||
assert.ok(
|
||||
exists(".show-advanced-search"),
|
||||
"it shows full page search button"
|
||||
);
|
||||
|
||||
assert.notOk(exists(".menu-panel"), "Menu panel is not rendered yet");
|
||||
|
||||
await click("#search-term");
|
||||
|
||||
assert.ok(
|
||||
exists(".menu-panel .search-menu-initial-options"),
|
||||
"Menu panel is rendered with initial options"
|
||||
);
|
||||
|
||||
await fillIn("#search-term", "test");
|
||||
|
||||
assert.strictEqual(
|
||||
query(".label-suffix").textContent.trim(),
|
||||
I18n.t("search.in_topics_posts"),
|
||||
"search label reflects context of search"
|
||||
);
|
||||
|
||||
await triggerKeyEvent("#search-term", "keyup", "Enter");
|
||||
|
||||
assert.ok(
|
||||
exists(".search-result-topic"),
|
||||
"search result is a list of topics"
|
||||
);
|
||||
|
||||
await triggerKeyEvent("#search-term", "keyup", "Escape");
|
||||
|
||||
assert.notOk(exists(".menu-panel"), "Menu panel is gone");
|
||||
|
||||
await click("#search-term");
|
||||
await click("#search-term");
|
||||
|
||||
assert.ok(
|
||||
exists(".search-result-topic"),
|
||||
"Clicking the term brought back search results"
|
||||
);
|
||||
});
|
||||
});
|
@ -14,7 +14,8 @@
|
||||
$search-pad-vertical: 0.25em;
|
||||
$search-pad-horizontal: 0.5em;
|
||||
|
||||
.search-menu {
|
||||
.search-menu,
|
||||
.search-menu-container {
|
||||
.menu-panel .panel-body-contents {
|
||||
overflow-y: auto;
|
||||
}
|
||||
@ -60,6 +61,18 @@ $search-pad-horizontal: 0.5em;
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
&.menu-panel-results {
|
||||
position: relative;
|
||||
|
||||
.menu-panel {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: unset;
|
||||
width: unset;
|
||||
}
|
||||
}
|
||||
|
||||
.results {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
Loading…
x
Reference in New Issue
Block a user