DEV: PluginApi function to customize search menu assistant item behavior (#24992)

This commit is contained in:
Mark VanLandingham 2023-12-20 15:25:45 -06:00 committed by GitHub
parent 87883a1963
commit 3c6362bb26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 89 additions and 8 deletions

View File

@ -4,6 +4,7 @@
class="search-menu-assistant-item" class="search-menu-assistant-item"
{{on "keydown" this.onKeydown}} {{on "keydown" this.onKeydown}}
{{on "click" this.onClick}} {{on "click" this.onClick}}
data-usage={{@usage}}
> >
<a class="search-link" href={{this.href}}> <a class="search-link" href={{this.href}}>
<span aria-label={{i18n "search.title"}}> <span aria-label={{i18n "search.title"}}>

View File

@ -5,6 +5,15 @@ import { focusSearchInput } from "discourse/components/search-menu";
import getURL from "discourse-common/lib/get-url"; import getURL from "discourse-common/lib/get-url";
import { debounce } from "discourse-common/utils/decorators"; import { debounce } from "discourse-common/utils/decorators";
const _itemSelectCallbacks = [];
export function addItemSelectCallback(fn) {
_itemSelectCallbacks.push(fn);
}
export function resetItemSelectCallbacks() {
_itemSelectCallbacks.length = 0;
}
export default class AssistantItem extends Component { export default class AssistantItem extends Component {
@service search; @service search;
@service appEvents; @service appEvents;
@ -85,16 +94,32 @@ export default class AssistantItem extends Component {
@debounce(100) @debounce(100)
itemSelected() { itemSelected() {
let updatedValue = ""; let updatedTerm = "";
if (this.args.slug) { if (this.args.slug) {
updatedValue = this.prefix.concat(this.args.slug); updatedTerm = this.prefix.concat(this.args.slug);
} else { } else {
updatedValue = this.prefix.trim(); updatedTerm = this.prefix.trim();
} }
const inTopicContext = this.search.searchContext?.type === "topic"; const inTopicContext = this.search.searchContext?.type === "topic";
this.args.searchTermChanged(updatedValue, { const searchTopics = !inTopicContext || this.search.activeGlobalSearchTerm;
searchTopics: !inTopicContext || this.search.activeGlobalSearchTerm,
if (
_itemSelectCallbacks.length &&
!_itemSelectCallbacks.some((fn) =>
fn({
updatedTerm,
searchTermChanged: this.args.searchTermChanged,
usage: this.args.usage,
})
)
) {
// Return early if any callbacks return false
return;
}
this.args.searchTermChanged(updatedTerm, {
searchTopics,
...(inTopicContext && ...(inTopicContext &&
!this.args.searchAllTopics && { setTopicContext: true }), !this.args.searchAllTopics && { setTopicContext: true }),
}); });

View File

@ -48,7 +48,9 @@
/> />
{{/if}} {{/if}}
{{else}} {{else}}
<SearchMenu::Results::RandomQuickTip /> <SearchMenu::Results::RandomQuickTip
@searchTermChanged={{@searchTermChanged}}
/>
{{#if (and this.currentUser this.siteSettings.log_search_queries)}} {{#if (and this.currentUser this.siteSettings.log_search_queries)}}
<SearchMenu::Results::RecentSearches <SearchMenu::Results::RecentSearches
@closeSearchMenu={{@closeSearchMenu}} @closeSearchMenu={{@closeSearchMenu}}

View File

@ -64,7 +64,7 @@ export default class RandomQuickTip extends Component {
@action @action
tipSelected(e) { tipSelected(e) {
if (e.target.classList.contains("tip-clickable")) { if (e.target.classList.contains("tip-clickable")) {
this.search.activeGlobalSearchTerm = this.randomTip.label; this.args.searchTermChanged(this.randomTip.label);
focusSearchInput(); focusSearchInput();
e.stopPropagation(); e.stopPropagation();

View File

@ -17,6 +17,7 @@
@slug={{slug}} @slug={{slug}}
@closeSearchMenu={{@closeSearchMenu}} @closeSearchMenu={{@closeSearchMenu}}
@searchTermChanged={{@searchTermChanged}} @searchTermChanged={{@searchTermChanged}}
@usage="recent-search"
/> />
{{/each}} {{/each}}
</div> </div>

View File

@ -18,6 +18,7 @@ import {
} from "discourse/components/reviewable-item"; } from "discourse/components/reviewable-item";
import { addAdvancedSearchOptions } from "discourse/components/search-advanced-options"; import { addAdvancedSearchOptions } from "discourse/components/search-advanced-options";
import { addSearchSuggestion as addGlimmerSearchSuggestion } from "discourse/components/search-menu/results/assistant"; import { addSearchSuggestion as addGlimmerSearchSuggestion } from "discourse/components/search-menu/results/assistant";
import { addItemSelectCallback as addSearchMenuAssistantSelectCallback } from "discourse/components/search-menu/results/assistant-item";
import { import {
addQuickSearchRandomTip, addQuickSearchRandomTip,
removeDefaultQuickSearchRandomTips, removeDefaultQuickSearchRandomTips,
@ -143,7 +144,7 @@ import { modifySelectKit } from "select-kit/mixins/plugin-api";
// docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version // docs/CHANGELOG-JAVASCRIPT-PLUGIN-API.md whenever you change the version
// using the format described at https://keepachangelog.com/en/1.0.0/. // using the format described at https://keepachangelog.com/en/1.0.0/.
export const PLUGIN_API_VERSION = "1.19.0"; export const PLUGIN_API_VERSION = "1.20.0";
// This helper prevents us from applying the same `modifyClass` over and over in test mode. // This helper prevents us from applying the same `modifyClass` over and over in test mode.
function canModify(klass, type, resolverName, changes) { function canModify(klass, type, resolverName, changes) {
@ -1830,6 +1831,26 @@ class PluginApi {
addGlimmerSearchSuggestion(value); addGlimmerSearchSuggestion(value);
} }
/**
* Add a callback that will be evaluated when search menu assistant-items are clicked. Function
* takes an object as it's only argument. This object includes the updated term, searchTermChanged function,
* and the usage. If any callbacks return false, the core logic will be halted
*
* ```
* api.addSearchMenuAssistantSelectCallback((args) => {
* if (args.usage !== "recent-search") {
* return true;
* }
* args.searchTermChanged(args.updatedTerm)
* return false;
* })
* ```
*
*/
addSearchMenuAssistantSelectCallback(fn) {
addSearchMenuAssistantSelectCallback(fn);
}
/** /**
* Force a given menu panel (search-menu, user-menu) to be displayed as dropdown if ANY of the passed `classNames` are included in the `classList` of a menu panel. * Force a given menu panel (search-menu, user-menu) to be displayed as dropdown if ANY of the passed `classNames` are included in the `classList` of a menu panel.
* This can be useful for plugins as the default behavior is to add a 'slide-in' behavior to a menu panel if you are viewing on a small screen. eg. mobile. * This can be useful for plugins as the default behavior is to add a 'slide-in' behavior to a menu panel if you are viewing on a small screen. eg. mobile.

View File

@ -7,6 +7,7 @@ import {
} from "@ember/test-helpers"; } from "@ember/test-helpers";
import { test } from "qunit"; import { test } from "qunit";
import { DEFAULT_TYPE_FILTER } from "discourse/components/search-menu"; import { DEFAULT_TYPE_FILTER } from "discourse/components/search-menu";
import { withPluginApi } from "discourse/lib/plugin-api";
import searchFixtures from "discourse/tests/fixtures/search-fixtures"; import searchFixtures from "discourse/tests/fixtures/search-fixtures";
import { import {
acceptance, acceptance,
@ -716,6 +717,28 @@ acceptance("Search - Glimmer - Authenticated", function (needs) {
"shows second recent search" "shows second recent search"
); );
}); });
test("initial options - overriding behavior with addSearchMenuAssistantSelectCallback", async function (assert) {
await visit("/");
await click("#search-button");
withPluginApi("1.20.0", (api) => {
api.addSearchMenuAssistantSelectCallback((args) => {
if (args.usage === "recent-search") {
args.searchTermChanged("hijacked!");
return false;
}
return true;
});
});
await click(
".search-menu .search-menu-recent li:nth-of-type(1) .search-link"
);
assert.strictEqual(query("#search-term").value, "hijacked!");
});
}); });
acceptance("Search - Glimmer - with tagging enabled", function (needs) { acceptance("Search - Glimmer - with tagging enabled", function (needs) {

View File

@ -21,6 +21,7 @@ import { clearToolbarCallbacks } from "discourse/components/d-editor";
import { clearBulkButtons } from "discourse/components/modal/topic-bulk-actions"; import { clearBulkButtons } from "discourse/components/modal/topic-bulk-actions";
import { resetWidgetCleanCallbacks } from "discourse/components/mount-widget"; import { resetWidgetCleanCallbacks } from "discourse/components/mount-widget";
import { resetDecorators as resetPluginOutletDecorators } from "discourse/components/plugin-connector"; import { resetDecorators as resetPluginOutletDecorators } from "discourse/components/plugin-connector";
import { resetItemSelectCallbacks } from "discourse/components/search-menu/results/assistant-item";
import { resetOnKeyUpCallbacks } from "discourse/components/search-menu/search-term"; import { resetOnKeyUpCallbacks } from "discourse/components/search-menu/search-term";
import { resetTopicTitleDecorators } from "discourse/components/topic-title"; import { resetTopicTitleDecorators } from "discourse/components/topic-title";
import { resetUserMenuProfileTabItems } from "discourse/components/user-menu/profile-tab-content"; import { resetUserMenuProfileTabItems } from "discourse/components/user-menu/profile-tab-content";
@ -229,6 +230,7 @@ export function testCleanup(container, app) {
clearExtraHeaderIcons(); clearExtraHeaderIcons();
resetOnKeyDownCallbacks(); resetOnKeyDownCallbacks();
resetOnKeyUpCallbacks(); resetOnKeyUpCallbacks();
resetItemSelectCallbacks();
resetUserMenuTabs(); resetUserMenuTabs();
resetLinkLookup(); resetLinkLookup();
resetModelTransformers(); resetModelTransformers();

View File

@ -7,6 +7,12 @@ in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.20.0] - 2023-12-20
### Added
- Added `addSearchMenuAssistantSelectCallback` function, which is used to override the behavior of clicking a search menu assistant item. If any callback returns false, the core behavior will not be executed.
## [1.19.0] - 2023-12-13 ## [1.19.0] - 2023-12-13
### Added ### Added