FIX: Consistent search shortcuts (#32099)

We now have 3 search UX variations:

* Header search
* Welcome banner search
* Icon search

And within each of these there is a `<SearchMenu />`
component with an input that needs to be focused based
on either Ctrl+F or `/` shortcuts. This commit makes
sure that each has a unique ID, and moves the functionality
of determining the "current" input ID and focusing the
input to the search service.

This fixes issues like where pressing Ctrl+F twice on
the header search would not reveal the regular browser
search.
This commit is contained in:
Martin Brennan
2025-04-04 11:54:46 +10:00
committed by GitHub
parent aaa648ceac
commit c4d971ea2c
23 changed files with 201 additions and 134 deletions

View File

@ -295,6 +295,7 @@ export default class GlimmerHeader extends Component {
<SearchMenuWrapper
@closeSearchMenu={{this.toggleSearchMenu}}
{{this.handleFocus}}
@searchInputId="icon-search-input"
/>
{{else if this.header.hamburgerVisible}}
<HamburgerDropdownWrapper

View File

@ -18,6 +18,7 @@ export default class Contents extends Component {
@service header;
@service router;
@service sidebarState;
@service search;
get sidebarIcon() {
if (this.sidebarState.adminSidebarAllowedWithLegacyNavigationMenu) {
@ -47,12 +48,10 @@ export default class Contents extends Component {
return false;
}
const searchExperience = applyValueTransformer(
"site-setting-search-experience",
this.siteSettings.search_experience
return (
this.search.searchExperience === "search_field" &&
!this.args.topicInfoVisible
);
return searchExperience === "search_field" && !this.args.topicInfoVisible;
}
<template>

View File

@ -2,7 +2,7 @@ import Component from "@glimmer/component";
import { service } from "@ember/service";
import { modifier } from "ember-modifier";
import DButton from "discourse/components/d-button";
import SearchMenu, { focusSearchInput } from "discourse/components/search-menu";
import SearchMenu from "discourse/components/search-menu";
import bodyClass from "discourse/helpers/body-class";
import concatClass from "discourse/helpers/concat-class";
@ -11,13 +11,14 @@ export default class HeaderSearch extends Component {
@service siteSettings;
@service currentUser;
@service appEvents;
@service search;
advancedSearchButtonHref = "/search?expanded=true";
handleKeyboardShortcut = modifier(() => {
const cb = (appEvent) => {
if (appEvent.type === "search" || appEvent.type === "page-search") {
focusSearchInput();
this.search.focusSearchInput();
appEvent.event.preventDefault();
}
};
@ -51,7 +52,10 @@ export default class HeaderSearch extends Component {
@href={{this.advancedSearchButtonHref}}
/>
<SearchMenu @location="header" />
<SearchMenu
@location="header"
@searchInputId="header-search-input"
/>
</div>
</div>
</div>

View File

@ -5,7 +5,6 @@ import { eq } from "truth-helpers";
import InterfaceColorSelector from "discourse/components/interface-color-selector";
import DAG from "discourse/lib/dag";
import getURL from "discourse/lib/get-url";
import { applyValueTransformer } from "discourse/lib/transformer";
import Dropdown from "./dropdown";
import UserDropdown from "./user-dropdown";
@ -60,14 +59,9 @@ export default class Icons extends Component {
return false;
}
const searchExperience = applyValueTransformer(
"site-setting-search-experience",
this.siteSettings.search_experience
);
return (
this.site.mobileView ||
searchExperience === "search_icon" ||
this.search.searchExperience === "search_icon" ||
this.args.topicInfoVisible
);
}

View File

@ -2,7 +2,10 @@ import SearchMenuPanel from "../search-menu-panel";
const SearchMenuWrapper = <template>
<div class="search-menu glimmer-search-menu" aria-live="polite" ...attributes>
<SearchMenuPanel @closeSearchMenu={{@closeSearchMenu}} />
<SearchMenuPanel
@searchInputId={{@searchInputId}}
@closeSearchMenu={{@closeSearchMenu}}
/>
</div>
</template>;

View File

@ -22,6 +22,7 @@ export default class SearchMenuPanel extends Component {
@inlineResults={{true}}
@autofocusInput={{true}}
@location="header"
@searchInputId={{@searchInputId}}
/>
</MenuPanel>
</template>

View File

@ -33,14 +33,10 @@ import userSearch from "discourse/lib/user-search";
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_INPUT_ID = "icon-search-input";
export const MODIFIER_REGEXP = /.*(\#|\@|:).*$/gi;
export const DEFAULT_TYPE_FILTER = "exclude_topics";
export function focusSearchInput(inputId = SEARCH_INPUT_ID) {
document.getElementById(inputId).focus();
}
export default class SearchMenu extends Component {
@service search;
@service currentUser;
@ -189,7 +185,7 @@ export default class SearchMenu extends Component {
e.stopPropagation();
e.preventDefault();
this.search.activeGlobalSearchTerm = "";
focusSearchInput();
this.search.focusSearchInput();
this.triggerSearch();
}
@ -452,6 +448,7 @@ export default class SearchMenu extends Component {
{{#if @inlineResults}}
<Results
@searchInputId={{@searchInputId}}
@loading={{this.loading}}
@invalidTerm={{this.invalidTerm}}
@suggestionKeyword={{this.suggestionKeyword}}
@ -467,6 +464,7 @@ export default class SearchMenu extends Component {
{{else if this.displayMenuPanelResults}}
<MenuPanel @panelClass="search-menu-panel">
<Results
@searchInputId={{@searchInputId}}
@loading={{this.loading}}
@invalidTerm={{this.invalidTerm}}
@suggestionKeyword={{this.suggestionKeyword}}

View File

@ -93,6 +93,7 @@ export default class Results extends Component {
<div class="no-results">{{i18n "search.no_results"}}</div>
{{else if this.renderInitialOptions}}
<InitialOptions
@searchInputId={{@searchInputId}}
@closeSearchMenu={{@closeSearchMenu}}
@searchTermChanged={{@searchTermChanged}}
/>
@ -100,6 +101,7 @@ export default class Results extends Component {
{{#if (and (not @searchTopics) (not @inPMInboxContext))}}
{{! render the first couple suggestions before a search has been performed}}
<InitialOptions
@searchInputId={{@searchInputId}}
@closeSearchMenu={{@closeSearchMenu}}
@searchTermChanged={{@searchTermChanged}}
/>

View File

@ -3,7 +3,6 @@ import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { and, or } from "truth-helpers";
import { focusSearchInput } from "discourse/components/search-menu";
import Category from "discourse/components/search-menu/results/type/category";
import Tag from "discourse/components/search-menu/results/type/tag";
import User from "discourse/components/search-menu/results/type/user";
@ -128,7 +127,7 @@ export default class AssistantItem extends Component {
...(inTopicContext &&
!this.args.searchAllTopics && { setTopicContext: true }),
});
focusSearchInput();
this.search.focusSearchInput();
}
<template>

View File

@ -213,7 +213,10 @@ export default class InitialOptions extends Component {
{{/if}}
{{/if}}
{{else}}
<RandomQuickTip @searchTermChanged={{@searchTermChanged}} />
<RandomQuickTip
@searchInputId={{@searchInputId}}
@searchTermChanged={{@searchTermChanged}}
/>
{{#if (and this.currentUser this.siteSettings.log_search_queries)}}
<RecentSearches
@closeSearchMenu={{@closeSearchMenu}}

View File

@ -2,7 +2,6 @@ import Component from "@glimmer/component";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { focusSearchInput } from "discourse/components/search-menu";
import concatClass from "discourse/helpers/concat-class";
import { i18n } from "discourse-i18n";
@ -67,7 +66,7 @@ export default class RandomQuickTip extends Component {
tipSelected(e) {
if (e.target.classList.contains("tip-clickable")) {
this.args.searchTermChanged(this.randomTip.label);
focusSearchInput();
this.search.focusSearchInput();
e.stopPropagation();
e.preventDefault();

View File

@ -5,7 +5,7 @@ import { htmlSafe } from "@ember/template";
import { modifier } from "ember-modifier";
import DButton from "discourse/components/d-button";
import PluginOutlet from "discourse/components/plugin-outlet";
import SearchMenu, { focusSearchInput } from "discourse/components/search-menu";
import SearchMenu from "discourse/components/search-menu";
import bodyClass from "discourse/helpers/body-class";
import { prioritizeNameFallback } from "discourse/lib/settings";
import { applyValueTransformer } from "discourse/lib/transformer";
@ -39,7 +39,7 @@ export default class WelcomeBanner extends Component {
(appEvent.type === "search" || appEvent.type === "page-search") &&
this.search.welcomeBannerSearchInViewport
) {
focusSearchInput("welcome-banner-search-term");
this.search.focusSearchInput();
appEvent.event.preventDefault();
}
};
@ -107,7 +107,7 @@ export default class WelcomeBanner extends Component {
/>
<SearchMenu
@location="welcome-banner"
@searchInputId="welcome-banner-search-term"
@searchInputId="welcome-banner-search-input"
/>
</div>
<PluginOutlet @name="welcome-banner-below-input" />

View File

@ -1,12 +1,13 @@
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import Service, { service } from "@ember/service";
import { focusSearchInput } from "discourse/components/search-menu";
import { disableImplicitInjections } from "discourse/lib/implicit-injections";
import { applyValueTransformer } from "discourse/lib/transformer";
@disableImplicitInjections
export default class Search extends Service {
@service appEvents;
@service siteSettings;
@tracked activeGlobalSearchTerm = "";
@tracked searchContext;
@ -20,6 +21,27 @@ export default class Search extends Service {
// only relative for the widget search menu
searchContextEnabled = false; // checkbox to scope search
get currentSearchInputId() {
if (this.welcomeBannerSearchInViewport) {
return "welcome-banner-search-input";
} else if (this.searchExperience === "search_field") {
return "header-search-input";
} else {
return "icon-search-input";
}
}
get searchExperience() {
return applyValueTransformer(
"site-setting-search-experience",
this.siteSettings.search_experience
);
}
focusSearchInput() {
document.getElementById(this.currentSearchInputId)?.focus();
}
get contextType() {
return this.searchContext?.type || null;
}
@ -97,7 +119,7 @@ export default class Search extends Service {
const firstResult = results[0] || links[0];
firstResult.focus();
} else if (index === 0 && e.key === "ArrowUp") {
focusSearchInput();
this.focusSearchInput();
} else if (index > -1) {
// change focus to the next result item if present
index += e.key === "ArrowDown" ? 1 : -1;

View File

@ -269,7 +269,7 @@ acceptance("Group - Authenticated", function (needs) {
);
await click("#search-button");
await fillIn("#search-term", "something");
await fillIn("#icon-search-input", "something");
assert
.dom(".search-menu .btn.search-context")

View File

@ -56,14 +56,14 @@ acceptance("Search - Anonymous", function (needs) {
await visit("/");
await click("#search-button");
assert.dom("#search-term").exists("shows the search input");
assert.dom("#icon-search-input").exists("shows the search input");
assert.dom(".show-advanced-search").exists("shows full page search button");
});
test("random quick tips", async function (assert) {
await visit("/");
await click("#search-button");
await fillIn("#search-term", "dev");
await fillIn("#icon-search-input", "dev");
assert
.dom(".search-menu .results ul li.search-random-quick-tip")
@ -73,8 +73,8 @@ acceptance("Search - Anonymous", function (needs) {
test("advanced search", async function (assert) {
await visit("/");
await click("#search-button");
await fillIn("#search-term", "dev");
await triggerKeyEvent("#search-term", "keyup", "ArrowDown");
await fillIn("#icon-search-input", "dev");
await triggerKeyEvent("#icon-search-input", "keyup", "ArrowDown");
await click(document.activeElement);
await click(".show-advanced-search");
@ -113,7 +113,7 @@ acceptance("Search - Anonymous", function (needs) {
test("initial options", async function (assert) {
await visit("/");
await click("#search-button");
await fillIn("#search-term", "dev");
await fillIn("#icon-search-input", "dev");
assert
.dom(
@ -152,7 +152,7 @@ acceptance("Search - Anonymous", function (needs) {
.dom(".search-link .search-item-tag")
.hasText("important", "first option includes tag");
await fillIn("#search-term", "smth");
await fillIn("#icon-search-input", "smth");
const secondOption = queryAll(contextSelector)[1];
assert
@ -172,7 +172,7 @@ acceptance("Search - Anonymous", function (needs) {
const contextSelector = ".search-menu .results .search-menu-assistant-item";
await visit("/c/bug");
await click("#search-button");
await fillIn("#search-term", "smth");
await fillIn("#icon-search-input", "smth");
const secondOption = queryAll(contextSelector)[1];
assert
@ -196,7 +196,7 @@ acceptance("Search - Anonymous", function (needs) {
const contextSelector = ".search-menu .results .search-menu-assistant-item";
await visit("/t/internationalization-localization/280");
await click("#search-button");
await fillIn("#search-term", "smth");
await fillIn("#icon-search-input", "smth");
const secondOption = queryAll(contextSelector)[1];
assert
@ -211,7 +211,7 @@ acceptance("Search - Anonymous", function (needs) {
test("initial options - topic search scope - 'in all topics' searches in all topics", async function (assert) {
await visit("/t/internationalization-localization/280/1");
await click("#search-button");
await fillIn("#search-term", "foo");
await fillIn("#icon-search-input", "foo");
// select 'in all topics and posts'
await click(
".search-menu .results .search-menu-initial-options .search-menu-assistant-item:first-child"
@ -224,7 +224,7 @@ acceptance("Search - Anonymous", function (needs) {
test("initial options - topic search scope - 'in this topic' searches posts within topic", async function (assert) {
await visit("/t/internationalization-localization/280/1");
await click("#search-button");
await fillIn("#search-term", "foo");
await fillIn("#icon-search-input", "foo");
// select 'in this topic'
await click(
".search-menu .results .search-menu-initial-options .search-menu-assistant-item:nth-child(2)"
@ -237,7 +237,7 @@ acceptance("Search - Anonymous", function (needs) {
test("initial options - topic search scope - keep 'in this topic' filter in full page search", async function (assert) {
await visit("/t/internationalization-localization/280/1");
await click("#search-button");
await fillIn("#search-term", "proper");
await fillIn("#icon-search-input", "proper");
await triggerKeyEvent(document.activeElement, "keyup", "ArrowDown");
await triggerKeyEvent(document.activeElement, "keydown", "ArrowDown");
await click(document.activeElement);
@ -258,7 +258,7 @@ acceptance("Search - Anonymous", function (needs) {
test("initial options - topic search scope - special case when matching a single user", async function (assert) {
await visit("/t/internationalization-localization/280/1");
await click("#search-button");
await fillIn("#search-term", "@admin");
await fillIn("#icon-search-input", "@admin");
assert.dom(".search-menu-assistant-item").exists({ count: 2 });
assert
@ -284,7 +284,7 @@ acceptance("Search - Anonymous", function (needs) {
const contextSelector = ".search-menu .results .search-menu-assistant-item";
await visit("/u/eviltrout");
await click("#search-button");
await fillIn("#search-term", "smth");
await fillIn("#icon-search-input", "smth");
const secondOption = queryAll(contextSelector)[1];
assert
@ -302,8 +302,8 @@ acceptance("Search - Anonymous", function (needs) {
test("topic results", async function (assert) {
await visit("/");
await click("#search-button");
await fillIn("#search-term", "dev");
await triggerKeyEvent("#search-term", "keyup", "ArrowDown");
await fillIn("#icon-search-input", "dev");
await triggerKeyEvent("#icon-search-input", "keyup", "ArrowDown");
await click(document.activeElement);
assert
@ -318,7 +318,7 @@ acceptance("Search - Anonymous", function (needs) {
test("topic results - topic search scope", async function (assert) {
await visit("/t/internationalization-localization/280/1");
await click("#search-button");
await fillIn("#search-term", "a proper");
await fillIn("#icon-search-input", "a proper");
await triggerKeyEvent(document.activeElement, "keyup", "ArrowDown");
await triggerKeyEvent(document.activeElement, "keydown", "ArrowDown");
await click(document.activeElement);
@ -336,15 +336,15 @@ acceptance("Search - Anonymous", function (needs) {
.exists("search context indicator is visible");
await click(".clear-search");
assert.dom("#search-term").hasNoText("clear button works");
assert.dom("#icon-search-input").hasNoText("clear button works");
await click(".search-context");
assert
.dom(".search-menu .search-context")
.doesNotExist("search context indicator is no longer visible");
await fillIn("#search-term", "dev");
await focus("#search-term");
await fillIn("#icon-search-input", "dev");
await focus("#icon-search-input");
await triggerKeyEvent(document.activeElement, "keyup", "ArrowDown");
await triggerKeyEvent(document.activeElement, "keydown", "ArrowDown");
await click(document.activeElement);
@ -353,9 +353,9 @@ acceptance("Search - Anonymous", function (needs) {
.dom(".search-menu .search-context")
.exists("search context indicator is visible");
await fillIn("#search-term", "");
await focus("#search-term");
await triggerKeyEvent("#search-term", "keyup", "Backspace");
await fillIn("#icon-search-input", "");
await focus("#icon-search-input");
await triggerKeyEvent("#icon-search-input", "keyup", "Backspace");
assert
.dom(".search-menu .search-context")
@ -365,8 +365,8 @@ acceptance("Search - Anonymous", function (needs) {
test("topic results - search result escapes html in topic title", async function (assert) {
await visit("/");
await click("#search-button");
await fillIn("#search-term", "dev");
await triggerKeyEvent("#search-term", "keyup", "Enter");
await fillIn("#icon-search-input", "dev");
await triggerKeyEvent("#icon-search-input", "keyup", "Enter");
assert
.dom(
@ -378,8 +378,8 @@ acceptance("Search - Anonymous", function (needs) {
test("topic results - search result escapes emojis in topic title", async function (assert) {
await visit("/");
await click("#search-button");
await fillIn("#search-term", "dev");
await triggerKeyEvent("#search-term", "keyup", "Enter");
await fillIn("#icon-search-input", "dev");
await triggerKeyEvent("#icon-search-input", "keyup", "Enter");
assert
.dom(".search-menu .search-result-topic .item .topic-title img[alt='+1']")
@ -390,7 +390,7 @@ acceptance("Search - Anonymous", function (needs) {
await visit("/");
await click("#search-button");
await focus(".show-advanced-search");
await triggerKeyEvent("#search-term", "keydown", "Escape");
await triggerKeyEvent("#icon-search-input", "keydown", "Escape");
assert.dom(".search-menu-panel").doesNotExist();
});
@ -487,14 +487,14 @@ acceptance("Search - Authenticated", function (needs) {
test("topic results - topic search scope - works with empty result sets", async function (assert) {
await visit("/t/internationalization-localization/280");
await click("#search-button");
await fillIn("#search-term", "plans");
await triggerKeyEvent("#search-term", "keyup", "ArrowDown");
await fillIn("#icon-search-input", "plans");
await triggerKeyEvent("#icon-search-input", "keyup", "ArrowDown");
await click(document.activeElement);
assert.dom(".search-menu .results .item").exists();
await fillIn("#search-term", "plans empty");
await triggerKeyEvent("#search-term", "keyup", 13);
await fillIn("#icon-search-input", "plans empty");
await triggerKeyEvent("#icon-search-input", "keyup", 13);
assert.dom(".search-menu .results .item").doesNotExist();
assert.dom(".search-menu .results .no-results").exists({ count: 1 });
@ -506,7 +506,7 @@ acceptance("Search - Authenticated", function (needs) {
test("topic results - topic search scope - clicking a search result navigates to topic url", async function (assert) {
await visit("/");
await click("#search-button");
await fillIn("#search-term", "Development");
await fillIn("#icon-search-input", "Development");
await triggerKeyEvent(document.activeElement, "keyup", "Enter");
assert
@ -525,8 +525,8 @@ acceptance("Search - Authenticated", function (needs) {
this.siteSettings.use_pg_headlines_for_excerpt = true;
await visit("/");
await click("#search-button");
await fillIn("#search-term", "dev");
await triggerKeyEvent("#search-term", "keyup", "Enter");
await fillIn("#icon-search-input", "dev");
await triggerKeyEvent("#icon-search-input", "keyup", "Enter");
assert
.dom(
@ -539,13 +539,13 @@ acceptance("Search - Authenticated", function (needs) {
const container = ".search-menu .results";
await visit("/");
await click("#search-button");
await fillIn("#search-term", "dev");
await fillIn("#icon-search-input", "dev");
assert.dom(`${container} ul li`).exists("has a list of items");
await triggerKeyEvent("#search-term", "keyup", "Enter");
await triggerKeyEvent("#icon-search-input", "keyup", "Enter");
assert.dom(`${container} .search-result-topic`).exists("has topic results");
await triggerKeyEvent("#search-term", "keyup", "ArrowDown");
await triggerKeyEvent("#icon-search-input", "keyup", "ArrowDown");
assert
.dom(`${container} li:first-child a`)
.isFocused("arrow down selects first element");
@ -568,7 +568,7 @@ acceptance("Search - Authenticated", function (needs) {
"arrow down sets focus to more results link"
);
await triggerKeyEvent("#search-term", "keydown", "Escape");
await triggerKeyEvent("#icon-search-input", "keydown", "Escape");
assert
.dom("#search-button")
.isFocused("Escaping search returns focus to search button");
@ -585,10 +585,10 @@ acceptance("Search - Authenticated", function (needs) {
"arrow up sets focus to search term input"
);
await triggerKeyEvent("#search-term", "keyup", "Enter");
await triggerKeyEvent("#icon-search-input", "keyup", "Enter");
assert.dom(`${container} .search-result-topic`).exists("has topic results");
await triggerKeyEvent("#search-term", "keyup", "Enter");
await triggerKeyEvent("#icon-search-input", "keyup", "Enter");
assert
.dom(".search-container")
.exists("second Enter hit goes to full page search");
@ -600,7 +600,7 @@ acceptance("Search - Authenticated", function (needs) {
await click("#search-button");
assert.dom(`${container} ul li`).exists("has a list of items");
await triggerKeyEvent("#search-term", "keyup", "Enter");
await triggerKeyEvent("#icon-search-input", "keyup", "Enter");
assert.dom(`.search-menu`).exists("search dropdown is visible");
});
@ -609,8 +609,8 @@ acceptance("Search - Authenticated", function (needs) {
await click(".reply");
await fillIn(".d-editor-input", "a link");
await click("#search-button");
await fillIn("#search-term", "dev");
await triggerKeyEvent("#search-term", "keyup", "Enter");
await fillIn("#icon-search-input", "dev");
await triggerKeyEvent("#icon-search-input", "keyup", "Enter");
await triggerKeyEvent(document.activeElement, "keyup", "ArrowDown");
await triggerKeyEvent(document.activeElement, "keydown", 65); // maps to lowercase a
@ -631,7 +631,7 @@ acceptance("Search - Authenticated", function (needs) {
await visit("/");
await triggerKeyEvent(document, "keypress", "J");
await click("#search-button");
await fillIn("#search-term", "Development");
await fillIn("#icon-search-input", "Development");
await triggerKeyEvent(document.activeElement, "keyup", "Enter");
await triggerKeyEvent(document.activeElement, "keyup", "ArrowDown");
@ -667,7 +667,7 @@ acceptance("Search - Authenticated", function (needs) {
".search-menu .search-menu-recent li:nth-of-type(1) .search-link"
);
assert.dom("input#search-term").hasValue("yellow");
assert.dom("input#icon-search-input").hasValue("yellow");
});
test("initial options - overriding behavior with addSearchMenuAssistantSelectCallback", async function (assert) {
@ -689,7 +689,7 @@ acceptance("Search - Authenticated", function (needs) {
".search-menu .search-menu-recent li:nth-of-type(1) .search-link"
);
assert.dom("#search-term").hasValue("hijacked!");
assert.dom("#icon-search-input").hasValue("hijacked!");
});
test("initial options - search history - category context", async function (assert) {
@ -774,8 +774,8 @@ acceptance("Search - with tagging enabled", function (needs) {
test("topic results - displays tags", async function (assert) {
await visit("/");
await click("#search-button");
await fillIn("#search-term", "dev");
await triggerKeyEvent("#search-term", "keyup", 13);
await fillIn("#icon-search-input", "dev");
await triggerKeyEvent("#icon-search-input", "keyup", 13);
assert
.dom(".search-menu .results ul li:nth-of-type(1) .discourse-tags")
@ -785,7 +785,7 @@ acceptance("Search - with tagging enabled", function (needs) {
test("initial options - topic search scope - selecting a tag defaults to searching 'in all topics'", async function (assert) {
await visit("/t/internationalization-localization/280/1");
await click("#search-button");
await fillIn("#search-term", "#dev");
await fillIn("#icon-search-input", "#dev");
await click(
".search-menu .results .search-menu-assistant .search-menu-assistant-item:nth-child(1)"
);
@ -797,7 +797,7 @@ acceptance("Search - with tagging enabled", function (needs) {
test("initial results - displays tag shortcuts", async function (assert) {
await visit("/");
await click("#search-button");
await fillIn("#search-term", "dude #monk");
await fillIn("#icon-search-input", "dude #monk");
const firstItem =
".search-menu .results ul.search-menu-assistant .search-link";
@ -832,7 +832,7 @@ acceptance("Search - with tagging enabled", function (needs) {
test("initial options - tag and category search scope - updates tag / category combination search suggestion when typing", async function (assert) {
await visit("/tags/c/bug/dev");
await click("#search-button");
await fillIn("#search-term", "foo bar");
await fillIn("#icon-search-input", "foo bar");
assert
.dom(".search-menu .results ul.search-menu-assistant .search-item-prefix")
@ -870,7 +870,7 @@ acceptance("Search - with tagging enabled", function (needs) {
test("initial options - tag intersection search scope - updates tag intersection search suggestion when typing", async function (assert) {
await visit("/tags/intersection/dev/foo");
await click("#search-button");
await fillIn("#search-term", "foo bar");
await fillIn("#icon-search-input", "foo bar");
assert
.dom(".search-menu .results ul.search-menu-assistant .search-item-prefix")
@ -1062,7 +1062,7 @@ acceptance("Search - assistant", function (needs) {
test("initial options - shows category shortcuts when typing #", async function (assert) {
await visit("/");
await click("#search-button");
await fillIn("#search-term", "#");
await fillIn("#icon-search-input", "#");
assert
.dom(
@ -1077,12 +1077,12 @@ acceptance("Search - assistant", function (needs) {
const firstTarget =
".search-menu .results ul.search-menu-assistant .search-link";
await fillIn("#search-term", "in:");
await fillIn("#icon-search-input", "in:");
assert
.dom(`${firstTarget} .search-item-slug`)
.hasText("in:title", "keyword is present in suggestion");
await fillIn("#search-term", "sam in:");
await fillIn("#icon-search-input", "sam in:");
assert
.dom(`${firstTarget} .search-item-prefix`)
.hasText("sam", "term is present in suggestion");
@ -1090,14 +1090,14 @@ acceptance("Search - assistant", function (needs) {
.dom(`${firstTarget} .search-item-slug`)
.hasText("in:title", "keyword is present in suggestion");
await fillIn("#search-term", "in:mess");
await fillIn("#icon-search-input", "in:mess");
assert.dom(firstTarget).hasText("in:messages");
});
test("initial options - user search scope - shows users when typing @", async function (assert) {
await visit("/");
await click("#search-button");
await fillIn("#search-term", "@");
await fillIn("#icon-search-input", "@");
assert
.dom(
".search-menu .results ul.search-menu-assistant .search-item-user .username"
@ -1114,13 +1114,13 @@ acceptance("Search - assistant", function (needs) {
".search-menu .results ul.search-menu-assistant .search-item-user"
);
assert.dom("#search-term").hasValue(`@${username}`);
assert.dom("#icon-search-input").hasValue(`@${username}`);
});
test("initial options - topic search scope - selecting a tag defaults to searching 'in all topics'", async function (assert) {
await visit("/t/internationalization-localization/280/1");
await click("#search-button");
await fillIn("#search-term", "@");
await fillIn("#icon-search-input", "@");
await click(
".search-menu .results .search-menu-assistant .search-menu-assistant-item:nth-child(1)"
);
@ -1154,9 +1154,9 @@ acceptance("Search - assistant", function (needs) {
assert.dom(".btn.search-context").exists("shows the button");
await fillIn("#search-term", "");
await focus("input#search-term");
await triggerKeyEvent("input#search-term", "keyup", "Backspace");
await fillIn("#icon-search-input", "");
await focus("input#icon-search-input");
await triggerKeyEvent("input#icon-search-input", "keyup", "Backspace");
assert.dom(".btn.search-context").doesNotExist("removes the button");
@ -1166,9 +1166,9 @@ acceptance("Search - assistant", function (needs) {
.dom(".btn.search-context")
.exists("shows the button when reinvoking search");
await fillIn("#search-term", "emoji");
await focus("input#search-term");
await triggerKeyEvent("#search-term", "keyup", "Enter");
await fillIn("#icon-search-input", "emoji");
await focus("input#icon-search-input");
await triggerKeyEvent("#icon-search-input", "keyup", "Enter");
assert
.dom(".search-menu .search-result-topic")
@ -1178,7 +1178,7 @@ acceptance("Search - assistant", function (needs) {
test("topic results - updates search term when selecting an initial category option", async function (assert) {
await visit("/");
await click("#search-button");
await fillIn("#search-term", "sam #");
await fillIn("#icon-search-input", "sam #");
const firstCategory =
".search-menu .results ul.search-menu-assistant .search-link";
@ -1188,13 +1188,13 @@ acceptance("Search - assistant", function (needs) {
await click(`${firstCategory} .badge-category__name`);
assert.dom("#search-term").hasValue(`sam #${firstCategoryName}`);
assert.dom("#icon-search-input").hasValue(`sam #${firstCategoryName}`);
});
test("topic results - soft loads the topic results after closing the search menu", async function (assert) {
await visit("/");
await click("#search-button");
await fillIn("#search-term", "Development mode");
await fillIn("#icon-search-input", "Development mode");
// navigate to topic and close search menu
const firstTopicResult = ".search-menu .results .search-result-topic a";

View File

@ -29,7 +29,11 @@ module("Integration | Component | search-menu", function (hooks) {
return response(searchFixtures["search/query"]);
});
await render(<template><SearchMenu @location="test" /></template>);
await render(
<template>
<SearchMenu @location="test" @searchInputId="icon-search-input" />
</template>
);
assert
.dom(".show-advanced-search")
@ -37,13 +41,13 @@ module("Integration | Component | search-menu", function (hooks) {
assert.dom(".menu-panel").doesNotExist("Menu panel is not rendered yet");
await click("#search-term");
await click("#icon-search-input");
assert
.dom(".menu-panel .search-menu-initial-options")
.exists("Menu panel is rendered with initial options");
await fillIn("#search-term", "test");
await fillIn("#icon-search-input", "test");
assert
.dom(".label-suffix")
@ -52,18 +56,18 @@ module("Integration | Component | search-menu", function (hooks) {
"search label reflects context of search"
);
await triggerKeyEvent("#search-term", "keyup", "Enter");
await triggerKeyEvent("#icon-search-input", "keyup", "Enter");
assert
.dom(".search-result-topic")
.exists("search result is a list of topics");
await triggerKeyEvent("#search-term", "keydown", "Escape");
await triggerKeyEvent("#icon-search-input", "keydown", "Escape");
assert.dom(".menu-panel").doesNotExist("Menu panel is gone");
await click("#search-term");
await click("#search-term");
await click("#icon-search-input");
await click("#icon-search-input");
assert
.dom(".search-result-topic")
@ -73,13 +77,16 @@ module("Integration | Component | search-menu", function (hooks) {
test("clicking outside results hides and blurs input", async function (assert) {
await render(
<template>
<div id="click-me"><SearchMenu @location="test" /></div>
<div id="click-me"><SearchMenu
@location="test"
@searchInputId="icon-search-input"
/></div>
</template>
);
await click("#search-term");
await click("#icon-search-input");
assert
.dom("#search-term")
.dom("#icon-search-input")
.isFocused("Clicking the search term input focuses it");
await click("#click-me");

View File

@ -30,7 +30,6 @@ $search-pad-horizontal: 0.5em;
margin: 1px;
padding: 0.25rem;
input#search-term,
input.search-term__input {
background: none;
border: 0;

View File

@ -59,7 +59,6 @@
}
.search-input {
#search-term,
.search-term__input {
min-width: 0;
flex: 1 1;

View File

@ -59,7 +59,6 @@
display: none;
}
.search-input .search-context ~ #search-term,
.search-input .search-context ~ .search-term__input {
padding-left: 0.5em;
}
@ -99,7 +98,6 @@
padding: 0 0 0 1.5em;
border-radius: var(--d-input-border-radius);
#search-term,
.search-term__input {
height: 100%;
width: 100%;

View File

@ -3,10 +3,6 @@
display: inline-block;
}
.search-menu .search-input input#search-term {
width: 100%;
}
.search-menu .search-input input.search-term__input {
width: 100%;
}

View File

@ -103,8 +103,8 @@ module PageObjects
page.has_css?(".search-menu-container .results .no-results")
end
def search_term
page.find("#search-term").value
def search_term(id = "icon-search-input")
page.find("##{id}").value
end
SEARCH_PAGE_SELECTOR = "body.search-page"

View File

@ -21,7 +21,7 @@ describe "Post small actions", type: :system do
expect(topic_page).to have_post_number(post.post_number)
find(".search-dropdown").click
find("#search-term").fill_in(with: "special")
find("#icon-search-input").fill_in(with: "special")
find(".search-menu-assistant-item:nth-child(2)").click

View File

@ -19,7 +19,7 @@ describe "Search | Shortcuts for variations of search input", type: :system do
expect(welcome_banner).to be_visible
page.send_keys("/")
expect(search_page).to have_search_menu
expect(current_active_element[:id]).to eq("welcome-banner-search-term")
expect(current_active_element[:id]).to eq("welcome-banner-search-input")
page.send_keys(:escape)
expect(search_page).to have_no_search_menu_visible
end
@ -29,11 +29,24 @@ describe "Search | Shortcuts for variations of search input", type: :system do
expect(welcome_banner).to be_visible
search_page.browser_search_shortcut
expect(search_page).to have_search_menu
expect(current_active_element[:id]).to eq("welcome-banner-search-term")
expect(current_active_element[:id]).to eq("welcome-banner-search-input")
page.send_keys(:escape)
expect(search_page).to have_no_search_menu_visible
end
it "displays and focuses welcome banner search when Ctrl+F is pressed and blurs it when Ctrl+F is pressed" do
visit("/")
expect(welcome_banner).to be_visible
search_page.browser_search_shortcut
expect(search_page).to have_search_menu
expect(current_active_element[:id]).to eq("welcome-banner-search-input")
# NOTE: This does not work as expected because pressing Ctrl+F in the browser does
# not change the document acive element, leaving it here as a reminder to manually test
# this behavior.
# search_page.browser_search_shortcut
# expect(current_active_element[:id]).to eq(nil)
end
context "when welcome banner is not in the viewport" do
before do
visit("/")
@ -44,7 +57,7 @@ describe "Search | Shortcuts for variations of search input", type: :system do
expect(welcome_banner).to be_invisible
page.send_keys("/")
expect(search_page).to have_search_menu
expect(current_active_element[:id]).to eq("search-term")
expect(current_active_element[:id]).to eq("header-search-input")
page.send_keys(:escape)
expect(search_page).to have_no_search_menu_visible
end
@ -53,10 +66,22 @@ describe "Search | Shortcuts for variations of search input", type: :system do
expect(welcome_banner).to be_invisible
search_page.browser_search_shortcut
expect(search_page).to have_search_menu
expect(current_active_element[:id]).to eq("search-term")
expect(current_active_element[:id]).to eq("header-search-input")
page.send_keys(:escape)
expect(search_page).to have_no_search_menu_visible
end
it "displays and focuses welcome banner search when Ctrl+F is pressed and blurs it when Ctrl+F is pressed" do
expect(welcome_banner).to be_invisible
search_page.browser_search_shortcut
expect(search_page).to have_search_menu
expect(current_active_element[:id]).to eq("header-search-input")
# NOTE: This does not work as expected because pressing Ctrl+F in the browser does
# not change the document acive element, leaving it here as a reminder to manually test
# this behavior.
# search_page.browser_search_shortcut
# expect(current_active_element[:id]).to eq(nil)
end
end
end
@ -68,7 +93,7 @@ describe "Search | Shortcuts for variations of search input", type: :system do
expect(welcome_banner).to be_hidden
page.send_keys("/")
expect(search_page).to have_search_menu
expect(current_active_element[:id]).to eq("search-term")
expect(current_active_element[:id]).to eq("header-search-input")
page.send_keys(:escape)
expect(search_page).to have_no_search_menu_visible
end
@ -78,7 +103,7 @@ describe "Search | Shortcuts for variations of search input", type: :system do
expect(welcome_banner).to be_hidden
search_page.browser_search_shortcut
expect(search_page).to have_search_menu
expect(current_active_element[:id]).to eq("search-term")
expect(current_active_element[:id]).to eq("header-search-input")
page.send_keys(:escape)
expect(search_page).to have_no_search_menu_visible
end
@ -96,7 +121,7 @@ describe "Search | Shortcuts for variations of search input", type: :system do
expect(welcome_banner).to be_visible
page.send_keys("/")
expect(search_page).to have_search_menu
expect(current_active_element[:id]).to eq("welcome-banner-search-term")
expect(current_active_element[:id]).to eq("welcome-banner-search-input")
page.send_keys(:escape)
expect(search_page).to have_no_search_menu_visible
end
@ -106,11 +131,25 @@ describe "Search | Shortcuts for variations of search input", type: :system do
expect(welcome_banner).to be_visible
search_page.browser_search_shortcut
expect(search_page).to have_search_menu
expect(current_active_element[:id]).to eq("welcome-banner-search-term")
expect(current_active_element[:id]).to eq("welcome-banner-search-input")
page.send_keys(:escape)
expect(search_page).to have_no_search_menu_visible
end
it "displays and focuses welcome banner search when Ctrl+F is pressed and blurs it when Ctrl+F is pressed" do
visit("/")
expect(welcome_banner).to be_visible
search_page.browser_search_shortcut
expect(search_page).to have_search_menu
expect(current_active_element[:id]).to eq("welcome-banner-search-input")
# NOTE: This does not work as expected because pressing Ctrl+F in the browser does
# not change the document acive element, leaving it here as a reminder to manually test
# this behavior.
# search_page.browser_search_shortcut
# expect(current_active_element[:id]).to eq(nil)
end
context "when welcome banner is not in the viewport" do
before do
visit("/")
@ -121,7 +160,7 @@ describe "Search | Shortcuts for variations of search input", type: :system do
expect(welcome_banner).to be_invisible
page.send_keys("/")
expect(search_page).to have_search_menu
expect(current_active_element[:id]).to eq("search-term")
expect(current_active_element[:id]).to eq("icon-search-input")
page.send_keys(:escape)
expect(search_page).to have_no_search_menu_visible
end
@ -136,7 +175,7 @@ describe "Search | Shortcuts for variations of search input", type: :system do
expect(welcome_banner).to be_hidden
page.send_keys("/")
expect(search_page).to have_search_menu
expect(current_active_element[:id]).to eq("search-term")
expect(current_active_element[:id]).to eq("icon-search-input")
page.send_keys(:escape)
expect(search_page).to have_no_search_menu_visible
end
@ -151,8 +190,12 @@ describe "Search | Shortcuts for variations of search input", type: :system do
visit "/t/#{topic.slug}/#{topic.id}"
search_page.browser_search_shortcut
expect(search_page).to have_search_menu_visible
search_page.browser_search_shortcut
expect(search_page).to have_no_search_menu_visible
expect(current_active_element[:id]).to eq("icon-search-input")
# NOTE: This does not work as expected because pressing Ctrl+F in the browser does
# not change the document acive element, leaving it here as a reminder to manually test
# this behavior.
# search_page.browser_search_shortcut
# expect(current_active_element[:id]).to eq(nil)
end
end
end