diff --git a/app/assets/javascripts/discourse/app/lib/plugin-api.js b/app/assets/javascripts/discourse/app/lib/plugin-api.js index a43744bffbc..34a273de1dd 100644 --- a/app/assets/javascripts/discourse/app/lib/plugin-api.js +++ b/app/assets/javascripts/discourse/app/lib/plugin-api.js @@ -95,9 +95,11 @@ import { replaceTagRenderer } from "discourse/lib/render-tag"; import { registerCustomLastUnreadUrlCallback } from "discourse/models/topic"; import { setNewCategoryDefaultColors } from "discourse/routes/new-category"; import { addSearchResultsCallback } from "discourse/lib/search"; +import { addOnKeyDownCallback } from "discourse/widgets/search-menu"; import { addQuickSearchRandomTip, addSearchSuggestion, + removeDefaultQuickSearchRandomTips, } from "discourse/widgets/search-menu-results"; import { CUSTOM_USER_SEARCH_OPTIONS } from "select-kit/components/user-chooser"; import { downloadCalendar } from "discourse/lib/download-calendar"; @@ -1707,6 +1709,25 @@ class PluginApi { downloadCalendar(title, dates); } + /** + * Add a function to be called when there is a keyDown even on the search-menu widget. + * This function runs before the default logic, and if one callback returns a falsey value + * the logic chain will stop, to prevent the core behavior from occuring. + * + * Example usage: + * ``` + * api.addSearchMenuOnKeyDownCallback((searchMenu, event) => { + * if (searchMenu.term === "stop") { + * return false; + * } + * }); + * ``` + * + */ + addSearchMenuOnKeyDownCallback(fn) { + addOnKeyDownCallback(fn); + } + /** * Add a quick search tip shown randomly when the search dropdown is invoked on desktop. * @@ -1726,6 +1747,19 @@ class PluginApi { addQuickSearchRandomTip(tip); } + /** + * Remove the default quick search tips shown randomly when the search dropdown is invoked on desktop. + * + * Usage: + * ``` + * api.removeDefaultQuickSearchRandomTips(); + * ``` + * + */ + removeDefaultQuickSearchRandomTips(tip) { + removeDefaultQuickSearchRandomTips(tip); + } + /** * Add custom user search options. * It is heavily correlated with `register_groups_callback_for_users_search_controller_action` which allows defining custom filter. diff --git a/app/assets/javascripts/discourse/app/widgets/search-menu-results.js b/app/assets/javascripts/discourse/app/widgets/search-menu-results.js index 70f20a255fd..243c0991dd3 100644 --- a/app/assets/javascripts/discourse/app/widgets/search-menu-results.js +++ b/app/assets/javascripts/discourse/app/widgets/search-menu-results.js @@ -8,12 +8,14 @@ import { dateNode } from "discourse/helpers/node"; import { emojiUnescape } from "discourse/lib/text"; import getURL from "discourse-common/lib/get-url"; import { h } from "virtual-dom"; -import hbs from "discourse/widgets/hbs-compiler"; +import widgetHbs from "discourse/widgets/hbs-compiler"; import highlightSearch from "discourse/lib/highlight-search"; import { iconNode } from "discourse-common/lib/icon-library"; import renderTag from "discourse/lib/render-tag"; import { MODIFIER_REGEXP } from "discourse/widgets/search-menu"; import User from "discourse/models/user"; +import { hbs } from "ember-cli-htmlbars"; +import RenderGlimmer from "discourse/widgets/render-glimmer"; const suggestionShortcuts = [ "in:title", @@ -73,6 +75,10 @@ export function addQuickSearchRandomTip(tip) { } } +export function removeDefaultQuickSearchRandomTips() { + QUICK_TIPS = QUICK_TIPS.filter((tip) => !DEFAULT_QUICK_TIPS.includes(tip)); +} + export function resetQuickSearchRandomTips() { QUICK_TIPS = [].concat(DEFAULT_QUICK_TIPS); } @@ -284,7 +290,14 @@ createWidget("search-menu-results", { tagName: "div.results", html(attrs) { - const { term, suggestionKeyword, results, searchTopics } = attrs; + const { + term, + suggestionKeyword, + inTopicContext, + results, + searchTopics, + onLinkClicked, + } = attrs; if (suggestionKeyword) { return this.attach("search-menu-assistant", { @@ -385,9 +398,41 @@ createWidget("search-menu-results", { } } + content.push( + new RenderGlimmer( + this, + "div", + hbs``, + { + outletArgs: { + searchTerm: term, + inTopicContext, + onLinkClicked, + searchTopics, + }, + } + ) + ); + content.push(categoriesAndTags); content.push(usersAndGroups); + content.push( + new RenderGlimmer( + this, + "div", + hbs``, + { + outletArgs: { + searchTerm: term, + inTopicContext, + onLinkClicked, + searchTopics, + }, + } + ) + ); + return content; }, }); @@ -778,6 +823,7 @@ createWidget("search-menu-assistant-item", { value: this.attrs.slug, searchTopics: true, setTopicContext: this.attrs.setTopicContext, + origin: this.attrs.origin, }); e.preventDefault(); return false; @@ -790,10 +836,16 @@ createWidget("random-quick-tip", { buildKey: () => "random-quick-tip", defaultState() { - return QUICK_TIPS[Math.floor(Math.random() * QUICK_TIPS.length)]; + return QUICK_TIPS.length + ? QUICK_TIPS[Math.floor(Math.random() * QUICK_TIPS.length)] + : {}; }, html(attrs, state) { + if (!Object.keys(state).length) { + return; + } + return [ h( `span.tip-label${state.clickable ? ".tip-clickable" : ""}`, @@ -819,7 +871,7 @@ createWidget("random-quick-tip", { createWidget("search-menu-recent-searches", { tagName: "div.search-menu-recent", - template: hbs` + template: widgetHbs`

{{i18n "search.recent"}}

{{flat-button @@ -833,7 +885,7 @@ createWidget("search-menu-recent-searches", { {{#each this.currentUser.recent_searches as |slug|}} {{attach widget="search-menu-assistant-item" - attrs=(hash slug=slug icon="history") + attrs=(hash slug=slug icon="history" origin="recent-search") }} {{/each}} `, diff --git a/app/assets/javascripts/discourse/app/widgets/search-menu.js b/app/assets/javascripts/discourse/app/widgets/search-menu.js index cf222e76506..fcbbc105843 100644 --- a/app/assets/javascripts/discourse/app/widgets/search-menu.js +++ b/app/assets/javascripts/discourse/app/widgets/search-menu.js @@ -27,6 +27,15 @@ export const DEFAULT_TYPE_FILTER = "exclude_topics"; const searchData = {}; +const onKeyDownCallbacks = []; + +export function addOnKeyDownCallback(fn) { + onKeyDownCallbacks.push(fn); +} +export function resetOnKeyDownCallbacks() { + onKeyDownCallbacks.clear(); +} + export function initSearchData() { searchData.loading = false; searchData.results = {}; @@ -322,6 +331,8 @@ export default createWidget("search-menu", { suggestionResults: searchData.suggestionResults, searchTopics: SearchHelper.includesTopics(), inPMInboxContext: this.state.inPMInboxContext, + inTopicContext: this.state.inTopicContext, + onLinkClicked: this.onLinkClicked.bind(this), }) ); } @@ -348,6 +359,10 @@ export default createWidget("search-menu", { }); }, + onLinkClicked() { + return this.sendWidgetAction("linkClickedEvent"); + }, + mouseDown(e) { if (e.target === document.querySelector("input#search-term")) { this.state.inputSelectionEvent = true; @@ -371,6 +386,18 @@ export default createWidget("search-menu", { }, keyDown(e) { + if ( + onKeyDownCallbacks.length && + !onKeyDownCallbacks.some((fn) => fn(this, e)) + ) { + // Return early if any callbacks return false + return; + } + + this.handleKeyDown(e); + }, + + handleKeyDown(e) { if (e.key === "Escape") { this.sendWidgetAction("toggleSearchMenu"); document.querySelector("#search-button").focus(); diff --git a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js index a395b4df421..252494d638b 100644 --- a/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js +++ b/app/assets/javascripts/discourse/tests/helpers/qunit-helpers.js @@ -26,7 +26,10 @@ import { _clearSnapshots } from "select-kit/components/composer-actions"; import { clearHTMLCache } from "discourse/helpers/custom-html"; import deprecated from "discourse-common/lib/deprecated"; import { restoreBaseUri } from "discourse-common/lib/get-url"; -import { initSearchData } from "discourse/widgets/search-menu"; +import { + initSearchData, + resetOnKeyDownCallbacks, +} from "discourse/widgets/search-menu"; import { resetPostMenuExtraButtons } from "discourse/widgets/post-menu"; import { isEmpty } from "@ember/utils"; import { resetCustomPostMessageCallbacks } from "discourse/controllers/topic"; @@ -213,6 +216,7 @@ export function testCleanup(container, app) { resetSidebarSection(); resetNotificationTypeRenderers(); clearExtraHeaderIcons(); + resetOnKeyDownCallbacks(); resetUserMenuTabs(); resetLinkLookup(); resetModelTransformers();