mirror of
https://github.com/discourse/discourse.git
synced 2025-06-21 00:45:26 +08:00
DEV: Restructure search menu so that it can be rendered outside of header (#23852)
This commit is contained in:

committed by
GitHub

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}}>
|
<div class={{this.classNames}} {{did-insert this.setupEventListeners}}>
|
||||||
<SearchMenu::MenuPanelContents
|
<div class="search-input">
|
||||||
@clearTopicContext={{this.clearTopicContext}}
|
{{#if this.search.inTopicContext}}
|
||||||
@clearPMInboxContext={{this.clearPMInboxContext}}
|
<DButton
|
||||||
@inPMInboxContext={{this.inPMInboxContext}}
|
@icon="times"
|
||||||
@searchTermChanged={{this.searchTermChanged}}
|
@label="search.in_this_topic"
|
||||||
@loading={{this.loading}}
|
@title="search.in_this_topic_tooltip"
|
||||||
@fullSearchUrl={{this.fullSearchUrl}}
|
@action={{this.clearTopicContext}}
|
||||||
@fullSearch={{this.fullSearch}}
|
class="btn-small search-context"
|
||||||
@triggerSearch={{this.triggerSearch}}
|
/>
|
||||||
@clearSearch={{this.clearSearch}}
|
{{else if this.inPMInboxContext}}
|
||||||
@includesTopics={{this.includesTopics}}
|
<DButton
|
||||||
@noResults={{this.noResults}}
|
@icon="times"
|
||||||
@results={{this.results}}
|
@label="search.in_messages"
|
||||||
@invalidTerm={{this.invalidTerm}}
|
@title="search.in_messages_tooltip"
|
||||||
@suggestionKeyword={{this.suggestionKeyword}}
|
@action={{this.clearPMInboxContext}}
|
||||||
@suggestionResults={{this.suggestionResults}}
|
class="btn-small search-context"
|
||||||
@searchTopics={{this.includesTopics}}
|
/>
|
||||||
@typeFilter={{this.typeFilter}}
|
{{/if}}
|
||||||
@updateTypeFilter={{this.updateTypeFilter}}
|
|
||||||
@closeSearchMenu={{@closeSearchMenu}}
|
<SearchMenu::SearchTerm
|
||||||
/>
|
@searchTermChanged={{this.searchTermChanged}}
|
||||||
</MenuPanel>
|
@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 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 SEARCH_INPUT_ID = "search-term";
|
export const SEARCH_INPUT_ID = "search-term";
|
||||||
export const SEARCH_BUTTON_ID = "search-button";
|
|
||||||
export const MODIFIER_REGEXP = /.*(\#|\@|:).*$/gi;
|
export const MODIFIER_REGEXP = /.*(\#|\@|:).*$/gi;
|
||||||
export const DEFAULT_TYPE_FILTER = "exclude_topics";
|
export const DEFAULT_TYPE_FILTER = "exclude_topics";
|
||||||
|
|
||||||
@ -30,16 +29,11 @@ export function focusSearchInput() {
|
|||||||
document.getElementById(SEARCH_INPUT_ID).focus();
|
document.getElementById(SEARCH_INPUT_ID).focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function focusSearchButton() {
|
|
||||||
document.getElementById(SEARCH_BUTTON_ID).focus();
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class SearchMenu extends Component {
|
export default class SearchMenu extends Component {
|
||||||
@service search;
|
@service search;
|
||||||
@service currentUser;
|
@service currentUser;
|
||||||
@service siteSettings;
|
@service siteSettings;
|
||||||
@service appEvents;
|
@service appEvents;
|
||||||
@service site;
|
|
||||||
|
|
||||||
@tracked loading = false;
|
@tracked loading = false;
|
||||||
@tracked results = {};
|
@tracked results = {};
|
||||||
@ -50,13 +44,42 @@ export default class SearchMenu extends Component {
|
|||||||
@tracked suggestionKeyword = false;
|
@tracked suggestionKeyword = false;
|
||||||
@tracked suggestionResults = [];
|
@tracked suggestionResults = [];
|
||||||
@tracked invalidTerm = false;
|
@tracked invalidTerm = false;
|
||||||
|
@tracked menuPanelOpen = false;
|
||||||
|
|
||||||
_debouncer = null;
|
_debouncer = null;
|
||||||
_activeSearch = null;
|
_activeSearch = null;
|
||||||
|
|
||||||
get animationClass() {
|
@bind
|
||||||
return this.site.mobileView || this.site.narrowDesktopView
|
setupEventListeners() {
|
||||||
? "slide-in"
|
document.addEventListener("mousedown", this.onDocumentPress, true);
|
||||||
: "drop-down";
|
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() {
|
get includesTopics() {
|
||||||
@ -71,6 +94,23 @@ export default class SearchMenu extends Component {
|
|||||||
return false;
|
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
|
@bind
|
||||||
fullSearchUrl(opts) {
|
fullSearchUrl(opts) {
|
||||||
let url = "/search";
|
let url = "/search";
|
||||||
@ -95,6 +135,18 @@ export default class SearchMenu extends Component {
|
|||||||
return getURL(url);
|
return getURL(url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get advancedSearchButtonHref() {
|
||||||
|
return this.fullSearchUrl({ expanded: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
get displayMenuPanelResults() {
|
||||||
|
if (this.args.inlineResults) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.menuPanelOpen;
|
||||||
|
}
|
||||||
|
|
||||||
@bind
|
@bind
|
||||||
clearSearch(e) {
|
clearSearch(e) {
|
||||||
e.stopPropagation();
|
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 (and this.search.inTopicContext (not @searchTopics))}}
|
||||||
{{#if @suggestionKeyword}}
|
<SearchMenu::BrowserSearchTip />
|
||||||
<SearchMenu::Results::Assistant
|
{{else if (not @loading)}}
|
||||||
@suggestionKeyword={{@suggestionKeyword}}
|
<div class="results">
|
||||||
@results={{@suggestionResults}}
|
{{#if @suggestionKeyword}}
|
||||||
@closeSearchMenu={{@closeSearchMenu}}
|
<SearchMenu::Results::Assistant
|
||||||
@searchTermChanged={{@searchTermChanged}}
|
@suggestionKeyword={{@suggestionKeyword}}
|
||||||
/>
|
@results={{@suggestionResults}}
|
||||||
{{else if this.termTooShort}}
|
@closeSearchMenu={{@closeSearchMenu}}
|
||||||
<div class="no-results">{{i18n "search.too_short"}}</div>
|
@searchTermChanged={{@searchTermChanged}}
|
||||||
{{else if this.noTopicResults}}
|
/>
|
||||||
<div class="no-results">{{i18n "search.no_results"}}</div>
|
{{else if this.termTooShort}}
|
||||||
{{else if this.renderInitialOptions}}
|
<div class="no-results">{{i18n "search.too_short"}}</div>
|
||||||
<SearchMenu::Results::InitialOptions
|
{{else if this.noTopicResults}}
|
||||||
@closeSearchMenu={{@closeSearchMenu}}
|
<div class="no-results">{{i18n "search.no_results"}}</div>
|
||||||
@searchTermChanged={{@searchTermChanged}}
|
{{else if this.renderInitialOptions}}
|
||||||
/>
|
<SearchMenu::Results::InitialOptions
|
||||||
{{else}}
|
@closeSearchMenu={{@closeSearchMenu}}
|
||||||
{{#if @searchTopics}}
|
@searchTermChanged={{@searchTermChanged}}
|
||||||
{{! 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}}
|
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#unless @inPMInboxContext}}
|
{{#if @searchTopics}}
|
||||||
{{! render the first couple suggestions before a search has been performed}}
|
{{! render results after a search has been performed }}
|
||||||
<SearchMenu::Results::InitialOptions
|
|
||||||
@closeSearchMenu={{@closeSearchMenu}}
|
|
||||||
@searchTermChanged={{@searchTermChanged}}
|
|
||||||
/>
|
|
||||||
{{#if this.resultTypesWithComponent}}
|
{{#if this.resultTypesWithComponent}}
|
||||||
<SearchMenu::Results::Types
|
<SearchMenu::Results::Types
|
||||||
|
@resultTypes={{this.resultTypesWithComponent}}
|
||||||
|
@topicResultsOnly={{true}}
|
||||||
|
@closeSearchMenu={{@closeSearchMenu}}
|
||||||
|
/>
|
||||||
|
<SearchMenu::Results::MoreLink
|
||||||
|
@updateTypeFilter={{@updateTypeFilter}}
|
||||||
|
@triggerSearch={{@triggerSearch}}
|
||||||
@resultTypes={{this.resultTypesWithComponent}}
|
@resultTypes={{this.resultTypesWithComponent}}
|
||||||
@closeSearchMenu={{@closeSearchMenu}}
|
@closeSearchMenu={{@closeSearchMenu}}
|
||||||
@searchTermChanged={{@searchTermChanged}}
|
@searchTermChanged={{@searchTermChanged}}
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/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}}
|
||||||
{{/if}}
|
</div>
|
||||||
</div>
|
{{/if}}
|
@ -3,10 +3,7 @@ import getURL from "discourse-common/lib/get-url";
|
|||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { debounce } from "discourse-common/utils/decorators";
|
import { debounce } from "discourse-common/utils/decorators";
|
||||||
import {
|
import { focusSearchInput } from "discourse/components/search-menu";
|
||||||
focusSearchButton,
|
|
||||||
focusSearchInput,
|
|
||||||
} from "discourse/components/search-menu";
|
|
||||||
|
|
||||||
export default class AssistantItem extends Component {
|
export default class AssistantItem extends Component {
|
||||||
@service search;
|
@service search;
|
||||||
@ -65,7 +62,6 @@ export default class AssistantItem extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
focusSearchButton();
|
|
||||||
this.args.closeSearchMenu();
|
this.args.closeSearchMenu();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return false;
|
return false;
|
||||||
|
@ -2,7 +2,11 @@
|
|||||||
{{! template-lint-disable no-invalid-interactive }}
|
{{! template-lint-disable no-invalid-interactive }}
|
||||||
<div class="search-menu__show-more" {{on "keyup" this.onKeyup}}>
|
<div class="search-menu__show-more" {{on "keyup" this.onKeyup}}>
|
||||||
{{#if this.moreUrl}}
|
{{#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"}}...
|
{{i18n "more"}}...
|
||||||
</a>
|
</a>
|
||||||
{{else if this.topicResults.more}}
|
{{else if this.topicResults.more}}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
|
import DiscourseURL from "discourse/lib/url";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
import { focusSearchButton } from "discourse/components/search-menu";
|
|
||||||
|
|
||||||
export default class MoreLink extends Component {
|
export default class MoreLink extends Component {
|
||||||
@service search;
|
@service search;
|
||||||
@ -17,16 +17,24 @@ export default class MoreLink extends Component {
|
|||||||
return this.topicResults.moreUrl && this.topicResults.moreUrl();
|
return this.topicResults.moreUrl && this.topicResults.moreUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
transitionToMoreUrl(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
this.args.closeSearchMenu();
|
||||||
|
DiscourseURL.routeTo(this.moreUrl);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
moreOfType(type) {
|
moreOfType(type) {
|
||||||
this.args.updateTypeFilter(type);
|
this.args.updateTypeFilter(type);
|
||||||
this.args.triggerSearch();
|
this.args.triggerSearch();
|
||||||
|
this.args.closeSearchMenu();
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
onKeyup(e) {
|
onKeyup(e) {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
focusSearchButton();
|
|
||||||
this.args.closeSearchMenu();
|
this.args.closeSearchMenu();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return false;
|
return false;
|
||||||
|
@ -2,7 +2,6 @@ import Component from "@glimmer/component";
|
|||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
import User from "discourse/models/user";
|
import User from "discourse/models/user";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { focusSearchButton } from "discourse/components/search-menu";
|
|
||||||
|
|
||||||
export default class RecentSearches extends Component {
|
export default class RecentSearches extends Component {
|
||||||
@service currentUser;
|
@service currentUser;
|
||||||
@ -32,7 +31,6 @@ export default class RecentSearches extends Component {
|
|||||||
@action
|
@action
|
||||||
onKeyup(e) {
|
onKeyup(e) {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
focusSearchButton();
|
|
||||||
this.args.closeSearchMenu();
|
this.args.closeSearchMenu();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return false;
|
return false;
|
||||||
|
@ -8,7 +8,11 @@
|
|||||||
{{! template-lint-disable no-pointer-down-event-binding }}
|
{{! template-lint-disable no-pointer-down-event-binding }}
|
||||||
{{! template-lint-disable no-invalid-interactive }}
|
{{! template-lint-disable no-invalid-interactive }}
|
||||||
<li class="item" {{on "keydown" this.onKeydown}}>
|
<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}} />
|
<resultType.component @result={{result}} />
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
import { action } from "@ember/object";
|
import { action } from "@ember/object";
|
||||||
import { focusSearchButton } from "discourse/components/search-menu";
|
|
||||||
|
|
||||||
export default class Types extends Component {
|
export default class Types extends Component {
|
||||||
@service search;
|
@service search;
|
||||||
@ -20,10 +19,14 @@ export default class Types extends Component {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
onClick() {
|
||||||
|
this.args.closeSearchMenu();
|
||||||
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
onKeydown(e) {
|
onKeydown(e) {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
focusSearchButton();
|
|
||||||
this.args.closeSearchMenu();
|
this.args.closeSearchMenu();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return false;
|
return false;
|
||||||
|
@ -7,5 +7,6 @@
|
|||||||
aria-label={{i18n "search.title"}}
|
aria-label={{i18n "search.title"}}
|
||||||
{{on "keyup" this.onKeyup}}
|
{{on "keyup" this.onKeyup}}
|
||||||
{{on "input" this.updateSearchTerm}}
|
{{on "input" this.updateSearchTerm}}
|
||||||
|
{{on "focus" @openSearchMenu}}
|
||||||
{{did-insert this.focus}}
|
{{did-insert this.focus}}
|
||||||
/>
|
/>
|
@ -6,7 +6,6 @@ import { inject as service } from "@ember/service";
|
|||||||
import {
|
import {
|
||||||
DEFAULT_TYPE_FILTER,
|
DEFAULT_TYPE_FILTER,
|
||||||
SEARCH_INPUT_ID,
|
SEARCH_INPUT_ID,
|
||||||
focusSearchButton,
|
|
||||||
} from "discourse/components/search-menu";
|
} from "discourse/components/search-menu";
|
||||||
|
|
||||||
const SECOND_ENTER_MAX_DELAY = 15000;
|
const SECOND_ENTER_MAX_DELAY = 15000;
|
||||||
@ -35,19 +34,22 @@ export default class SearchTerm extends Component {
|
|||||||
|
|
||||||
@action
|
@action
|
||||||
focus(element) {
|
focus(element) {
|
||||||
element.focus();
|
if (this.args.autofocus) {
|
||||||
element.select();
|
element.focus();
|
||||||
|
element.select();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
onKeyup(e) {
|
onKeyup(e) {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
focusSearchButton();
|
|
||||||
this.args.closeSearchMenu();
|
this.args.closeSearchMenu();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.args.openSearchMenu();
|
||||||
|
|
||||||
this.search.handleArrowUpOrDown(e);
|
this.search.handleArrowUpOrDown(e);
|
||||||
|
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
|
@ -12,7 +12,8 @@ import { wantsNewWindow } from "discourse/lib/intercept-click";
|
|||||||
import { logSearchLinkClick } from "discourse/lib/search";
|
import { logSearchLinkClick } from "discourse/lib/search";
|
||||||
import RenderGlimmer from "discourse/widgets/render-glimmer";
|
import RenderGlimmer from "discourse/widgets/render-glimmer";
|
||||||
import { hbs } from "ember-cli-htmlbars";
|
import { hbs } from "ember-cli-htmlbars";
|
||||||
import { SEARCH_BUTTON_ID } from "discourse/components/search-menu";
|
|
||||||
|
const SEARCH_BUTTON_ID = "search-button";
|
||||||
|
|
||||||
let _extraHeaderIcons = [];
|
let _extraHeaderIcons = [];
|
||||||
|
|
||||||
@ -393,14 +394,17 @@ createWidget("glimmer-search-menu-wrapper", {
|
|||||||
new RenderGlimmer(
|
new RenderGlimmer(
|
||||||
this,
|
this,
|
||||||
"div.widget-component-connector",
|
"div.widget-component-connector",
|
||||||
hbs`<SearchMenu @closeSearchMenu={{@data.closeSearchMenu}} />`,
|
hbs`<SearchMenuPanel @closeSearchMenu={{@data.closeSearchMenu}} />`,
|
||||||
{ closeSearchMenu: this.closeSearchMenu.bind(this) }
|
{
|
||||||
|
closeSearchMenu: this.closeSearchMenu.bind(this),
|
||||||
|
}
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
},
|
},
|
||||||
|
|
||||||
closeSearchMenu() {
|
closeSearchMenu() {
|
||||||
this.sendWidgetAction("toggleSearchMenu");
|
this.sendWidgetAction("toggleSearchMenu");
|
||||||
|
document.getElementById(SEARCH_BUTTON_ID)?.focus();
|
||||||
},
|
},
|
||||||
|
|
||||||
clickOutside() {
|
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-vertical: 0.25em;
|
||||||
$search-pad-horizontal: 0.5em;
|
$search-pad-horizontal: 0.5em;
|
||||||
|
|
||||||
.search-menu {
|
.search-menu,
|
||||||
|
.search-menu-container {
|
||||||
.menu-panel .panel-body-contents {
|
.menu-panel .panel-body-contents {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
}
|
}
|
||||||
@ -60,6 +61,18 @@ $search-pad-horizontal: 0.5em;
|
|||||||
margin-right: 0px;
|
margin-right: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.menu-panel-results {
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
.menu-panel {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: unset;
|
||||||
|
width: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.results {
|
.results {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
Reference in New Issue
Block a user