mirror of
				https://github.com/zulip/zulip.git
				synced 2025-10-31 20:13:46 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			235 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			235 lines
		
	
	
		
			9.0 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import $ from "jquery";
 | |
| 
 | |
| import {Filter} from "./filter";
 | |
| import * as message_view_header from "./message_view_header";
 | |
| import * as narrow from "./narrow";
 | |
| import * as narrow_state from "./narrow_state";
 | |
| import {page_params} from "./page_params";
 | |
| import * as search_pill from "./search_pill";
 | |
| import * as search_pill_widget from "./search_pill_widget";
 | |
| import * as search_suggestion from "./search_suggestion";
 | |
| import * as ui_util from "./ui_util";
 | |
| 
 | |
| // Exported for unit testing
 | |
| export let is_using_input_method = false;
 | |
| 
 | |
| export function narrow_or_search_for_term(search_string) {
 | |
|     const search_query_box = $("#search_query");
 | |
|     if (is_using_input_method) {
 | |
|         // Neither narrow nor search when using input tools as
 | |
|         // `updater` is also triggered when 'enter' is triggered
 | |
|         // while using input tool
 | |
|         return search_query_box.val();
 | |
|     }
 | |
|     ui_util.change_tab_to("#message_feed_container");
 | |
| 
 | |
|     let operators;
 | |
|     if (page_params.search_pills_enabled) {
 | |
|         // We have to take care to append the new pill before calling this
 | |
|         // function, so that the base_query includes the suggestion selected
 | |
|         // along with query corresponding to the existing pills.
 | |
|         const base_query = search_pill.get_search_string_for_current_filter(
 | |
|             search_pill_widget.widget,
 | |
|         );
 | |
|         operators = Filter.parse(base_query);
 | |
|     } else {
 | |
|         operators = Filter.parse(search_string);
 | |
|     }
 | |
|     narrow.activate(operators, {trigger: "search"});
 | |
| 
 | |
|     // It's sort of annoying that this is not in a position to
 | |
|     // blur the search box, because it means that Esc won't
 | |
|     // unnarrow, it'll leave the searchbox.
 | |
| 
 | |
|     // Narrowing will have already put some operators in the search box,
 | |
|     // so leave the current text in.
 | |
|     if (!page_params.search_pills_enabled) {
 | |
|         search_query_box.trigger("blur");
 | |
|     }
 | |
|     return search_query_box.val();
 | |
| }
 | |
| 
 | |
| function update_buttons_with_focus(focused) {
 | |
|     const search_query_box = $("#search_query");
 | |
| 
 | |
|     // Show buttons iff the search input is focused, or has non-empty contents,
 | |
|     // or we are narrowed.
 | |
|     if (focused || search_query_box.val() || narrow_state.active()) {
 | |
|         $(".search_button").prop("disabled", false);
 | |
|     }
 | |
| }
 | |
| 
 | |
| export function update_button_visibility() {
 | |
|     update_buttons_with_focus($("#search_query").is(":focus"));
 | |
| }
 | |
| 
 | |
| export function initialize() {
 | |
|     const search_query_box = $("#search_query");
 | |
|     const searchbox_form = $("#searchbox_form");
 | |
|     const searchbox = $("#searchbox");
 | |
| 
 | |
|     // Data storage for the typeahead.
 | |
|     // This maps a search string to an object with a "description" field.
 | |
|     // (It's a bit of legacy that we have an object with only one important
 | |
|     // field.  There's also a "search_string" field on each element that actually
 | |
|     // just represents the key of the hash, so it's redundant.)
 | |
|     let search_map = new Map();
 | |
| 
 | |
|     search_query_box.typeahead({
 | |
|         source(query) {
 | |
|             let base_query = "";
 | |
|             if (page_params.search_pills_enabled) {
 | |
|                 base_query = search_pill.get_search_string_for_current_filter(
 | |
|                     search_pill_widget.widget,
 | |
|                 );
 | |
|             }
 | |
|             const suggestions = search_suggestion.get_suggestions(base_query, query);
 | |
|             // Update our global search_map hash
 | |
|             search_map = suggestions.lookup_table;
 | |
|             return suggestions.strings;
 | |
|         },
 | |
|         fixed: true,
 | |
|         items: search_suggestion.max_num_of_search_results,
 | |
|         helpOnEmptyStrings: true,
 | |
|         naturalSearch: true,
 | |
|         highlighter(item) {
 | |
|             const obj = search_map.get(item);
 | |
|             return obj.description;
 | |
|         },
 | |
|         matcher() {
 | |
|             return true;
 | |
|         },
 | |
|         updater(search_string) {
 | |
|             if (page_params.search_pills_enabled) {
 | |
|                 search_pill.append_search_string(search_string, search_pill_widget.widget);
 | |
|                 return search_query_box.val();
 | |
|             }
 | |
|             return narrow_or_search_for_term(search_string);
 | |
|         },
 | |
|         sorter(items) {
 | |
|             return items;
 | |
|         },
 | |
|         stopAdvance: page_params.search_pills_enabled,
 | |
|         advanceKeyCodes: [8],
 | |
| 
 | |
|         on_move() {
 | |
|             if (page_params.search_pills_enabled) {
 | |
|                 ui_util.place_caret_at_end(search_query_box[0]);
 | |
|             }
 | |
|         },
 | |
|         // Use our custom typeahead `on_escape` hook to exit
 | |
|         // the search bar as soon as the user hits Esc.
 | |
|         on_escape: message_view_header.exit_search,
 | |
|     });
 | |
| 
 | |
|     searchbox_form.on("compositionend", () => {
 | |
|         // Set `is_using_input_method` to true if Enter is pressed to exit
 | |
|         // the input tool popover and get the text in the search bar. Then
 | |
|         // we suppress searching triggered by this Enter key by checking
 | |
|         // `is_using_input_method` before searching.
 | |
|         // More details in the commit message that added this line.
 | |
|         is_using_input_method = true;
 | |
|     });
 | |
| 
 | |
|     searchbox_form
 | |
|         .on("keydown", (e) => {
 | |
|             update_button_visibility();
 | |
|             const code = e.which;
 | |
|             if (code === 13 && search_query_box.is(":focus")) {
 | |
|                 // Don't submit the form so that the typeahead can instead
 | |
|                 // handle our Enter keypress. Any searching that needs
 | |
|                 // to be done will be handled in the keyup.
 | |
|                 e.preventDefault();
 | |
|             }
 | |
|         })
 | |
|         .on("keyup", (e) => {
 | |
|             if (is_using_input_method) {
 | |
|                 is_using_input_method = false;
 | |
|                 return;
 | |
|             }
 | |
|             const code = e.which;
 | |
|             if (code === 13 && search_query_box.is(":focus")) {
 | |
|                 // We just pressed Enter and the box had focus, which
 | |
|                 // means we didn't use the typeahead at all.  In that
 | |
|                 // case, we should act as though we're searching by
 | |
|                 // operators.  (The reason the other actions don't call
 | |
|                 // this codepath is that they first all blur the box to
 | |
|                 // indicate that they've done what they need to do)
 | |
| 
 | |
|                 // Pill is already added during keydown event of input pills.
 | |
|                 narrow_or_search_for_term(search_query_box.val());
 | |
|                 search_query_box.trigger("blur");
 | |
|                 update_buttons_with_focus(false);
 | |
|             }
 | |
|         });
 | |
| 
 | |
|     // Some of these functions don't actually need to be exported,
 | |
|     // but the code was moved here from elsewhere, and it would be
 | |
|     // more work to re-order everything and make them private.
 | |
| 
 | |
|     search_query_box.on("focus", focus_search);
 | |
|     search_query_box.on("blur", (e) => {
 | |
|         // The search query box is a visual cue as to
 | |
|         // whether search or narrowing is active.  If
 | |
|         // the user blurs the search box, then we should
 | |
|         // update the search string to reflect the current
 | |
|         // narrow (or lack of narrow).
 | |
|         //
 | |
|         // But we can't do this right away, because
 | |
|         // selecting something in the typeahead menu causes
 | |
|         // the box to lose focus a moment before.
 | |
|         //
 | |
|         // The workaround is to check 100ms later -- long
 | |
|         // enough for the search to have gone through, but
 | |
|         // short enough that the user won't notice (though
 | |
|         // really it would be OK if they did).
 | |
| 
 | |
|         if (page_params.search_pills_enabled) {
 | |
|             const pill_id = $(e.relatedTarget).closest(".pill").data("id");
 | |
|             const search_pill = search_pill_widget.widget.getByID(pill_id);
 | |
|             if (search_pill) {
 | |
|                 // The searchbox loses focus while the search
 | |
|                 // pill element gains focus.
 | |
|                 // We do not consider the searchbox to actually
 | |
|                 // lose focus when a pill inside it gets selected
 | |
|                 // or deleted by a click.
 | |
|                 return;
 | |
|             }
 | |
|         }
 | |
|         setTimeout(() => {
 | |
|             update_button_visibility();
 | |
|         }, 100);
 | |
|     });
 | |
| 
 | |
|     if (page_params.search_pills_enabled) {
 | |
|         // Uses jquery instead of pure css as the `:focus` event occurs on `#search_query`,
 | |
|         // while we want to add box-shadow to `#searchbox`. This could have been done
 | |
|         // with `:focus-within` CSS selector, but it is not supported in IE or Opera.
 | |
|         searchbox.on("focusout", () => {
 | |
|             message_view_header.close_search_bar_and_open_narrow_description();
 | |
|             searchbox.css({"box-shadow": "unset"});
 | |
|         });
 | |
|     }
 | |
| }
 | |
| 
 | |
| export function focus_search() {
 | |
|     // The search bar is not focused yet, but will be.
 | |
|     update_buttons_with_focus(true);
 | |
| }
 | |
| 
 | |
| export function initiate_search() {
 | |
|     message_view_header.open_search_bar_and_close_narrow_description();
 | |
|     $("#searchbox").css({"box-shadow": "inset 0px 0px 0px 2px hsl(204, 20%, 74%)"});
 | |
|     $("#search_query").typeahead("lookup").trigger("select");
 | |
|     if (page_params.search_pills_enabled) {
 | |
|         $("#search_query").trigger("focus");
 | |
|         ui_util.place_caret_at_end($("#search_query")[0]);
 | |
|     }
 | |
| }
 | |
| 
 | |
| export function clear_search_form() {
 | |
|     $("#search_query").val("");
 | |
|     $("#search_query").trigger("blur");
 | |
|     $(".search_button").prop("disabled", true);
 | |
| }
 |