mirror of https://github.com/zulip/zulip.git
typeahead: Make menu scrollable with up to 50 options.
To increase the number of options available for the user to pick from, we increase the limit of options shown to 50 for all typeaheads, and make the menu scrollable. The max height is set to original height of the composebox typeahead menu, which fit 8 options or to 95% of the window height, whichever is smaller. Fixes: #20620.
This commit is contained in:
parent
4cb925a9c8
commit
82c2da8aae
|
@ -165,6 +165,7 @@ import {insertTextIntoField} from "text-field-edit";
|
|||
import getCaretCoordinates from "textarea-caret";
|
||||
import * as tippy from "tippy.js";
|
||||
|
||||
import * as scroll_util from "./scroll_util";
|
||||
import {get_string_diff} from "./util";
|
||||
|
||||
function get_pseudo_keycode(
|
||||
|
@ -197,13 +198,15 @@ export function defaultSorter(items: string[], query: string): string[] {
|
|||
return [...beginswith, ...caseSensitive, ...caseInsensitive];
|
||||
}
|
||||
|
||||
export const MAX_ITEMS = 50;
|
||||
|
||||
/* TYPEAHEAD PUBLIC CLASS DEFINITION
|
||||
* ================================= */
|
||||
|
||||
const HEADER_ELEMENT_HTML =
|
||||
'<p class="typeahead-header"><span id="typeahead-header-text"></span></p>';
|
||||
const CONTAINER_HTML = '<div class="typeahead dropdown-menu"></div>';
|
||||
const MENU_HTML = '<ul class="typeahead-menu"></ul>';
|
||||
const MENU_HTML = '<ul class="typeahead-menu" data-simplebar></ul>';
|
||||
const ITEM_HTML = "<li><a></a></li>";
|
||||
const MIN_LENGTH = 1;
|
||||
|
||||
|
@ -275,7 +278,7 @@ export class Typeahead<ItemType extends string | object> {
|
|||
} else {
|
||||
assert(!this.input_element.$element.is("[contenteditable]"));
|
||||
}
|
||||
this.items = options.items ?? 8;
|
||||
this.items = options.items ?? MAX_ITEMS;
|
||||
this.matcher = options.matcher ?? ((item, query) => this.defaultMatcher(item, query));
|
||||
this.sorter = options.sorter;
|
||||
this.highlighter_html = options.highlighter_html;
|
||||
|
@ -538,7 +541,10 @@ export class Typeahead<ItemType extends string | object> {
|
|||
if (this.requireHighlight || this.shouldHighlightFirstResult()) {
|
||||
$items[0]!.addClass("active");
|
||||
}
|
||||
this.$menu.empty().append($items);
|
||||
// Getting scroll element ensures simplebar has processed the element
|
||||
// before we render it.
|
||||
scroll_util.get_scroll_element(this.$menu);
|
||||
scroll_util.get_content_element(this.$menu).empty().append($items);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -558,6 +564,7 @@ export class Typeahead<ItemType extends string | object> {
|
|||
}
|
||||
|
||||
$next.addClass("active");
|
||||
scroll_util.scroll_element_into_container($next, this.$menu);
|
||||
}
|
||||
|
||||
prev(): void {
|
||||
|
@ -576,6 +583,7 @@ export class Typeahead<ItemType extends string | object> {
|
|||
}
|
||||
|
||||
$prev.addClass("active");
|
||||
scroll_util.scroll_element_into_container($prev, this.$menu);
|
||||
}
|
||||
|
||||
listen(): void {
|
||||
|
@ -831,7 +839,7 @@ export class Typeahead<ItemType extends string | object> {
|
|||
|
||||
type TypeaheadOptions<ItemType> = {
|
||||
highlighter_html: (item: ItemType, query: string) => string | undefined;
|
||||
items: number;
|
||||
items?: number;
|
||||
source: (query: string, input_element: TypeaheadInputElement) => ItemType[];
|
||||
// optional options
|
||||
advanceKeyCodes?: number[];
|
||||
|
|
|
@ -6,7 +6,7 @@ import * as typeahead from "../shared/src/typeahead";
|
|||
import type {Emoji, EmojiSuggestion} from "../shared/src/typeahead";
|
||||
import render_topic_typeahead_hint from "../templates/topic_typeahead_hint.hbs";
|
||||
|
||||
import {Typeahead} from "./bootstrap_typeahead";
|
||||
import {MAX_ITEMS, Typeahead} from "./bootstrap_typeahead";
|
||||
import type {TypeaheadInputElement} from "./bootstrap_typeahead";
|
||||
import * as bulleted_numbered_list_util from "./bulleted_numbered_list_util";
|
||||
import * as compose_pm_pill from "./compose_pm_pill";
|
||||
|
@ -103,9 +103,8 @@ export type TypeaheadSuggestion =
|
|||
| EmojiSuggestion
|
||||
| SlashCommandSuggestion;
|
||||
|
||||
// This is what we use for direct message/compose typeaheads.
|
||||
// We export it to allow tests to mock it.
|
||||
export const max_num_items = 8;
|
||||
export const max_num_items = MAX_ITEMS;
|
||||
|
||||
export let emoji_collection: Emoji[] = [];
|
||||
|
||||
|
@ -1239,7 +1238,7 @@ export function initialize_topic_edit_typeahead(
|
|||
const stream_id = stream_data.get_stream_id(stream_name);
|
||||
return topics_seen_for(stream_id);
|
||||
},
|
||||
items: 5,
|
||||
items: max_num_items,
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1317,7 +1316,7 @@ export function initialize({
|
|||
source(): string[] {
|
||||
return topics_seen_for(compose_state.stream_id());
|
||||
},
|
||||
items: 3,
|
||||
items: max_num_items,
|
||||
highlighter_html(item: string): string {
|
||||
return typeahead_helper.render_typeahead_item({primary: item});
|
||||
},
|
||||
|
|
|
@ -202,7 +202,6 @@ export function initialize_custom_pronouns_type_fields(element_id: string): void
|
|||
type: "input" as const,
|
||||
};
|
||||
new Typeahead(bootstrap_typeahead_input, {
|
||||
items: 3,
|
||||
helpOnEmptyStrings: true,
|
||||
source() {
|
||||
return commonly_used_pronouns;
|
||||
|
|
|
@ -41,7 +41,6 @@ export function set_up_user(
|
|||
type: "contenteditable",
|
||||
};
|
||||
new Typeahead(bootstrap_typeahead_input, {
|
||||
items: 5,
|
||||
dropup: true,
|
||||
source(_query: string): UserPillData[] {
|
||||
return user_pill.typeahead_source(pills, exclude_bots);
|
||||
|
@ -87,7 +86,6 @@ export function set_up_stream(
|
|||
};
|
||||
opts.help_on_empty_strings ||= false;
|
||||
new Typeahead(bootstrap_typeahead_input, {
|
||||
items: 12,
|
||||
dropup: true,
|
||||
helpOnEmptyStrings: true,
|
||||
source(_query: string): StreamPillData[] {
|
||||
|
@ -153,7 +151,6 @@ export function set_up_combined(
|
|||
type: "contenteditable",
|
||||
};
|
||||
new Typeahead(bootstrap_typeahead_input, {
|
||||
items: 5,
|
||||
dropup: true,
|
||||
source(query: string): TypeaheadItem[] {
|
||||
let source: TypeaheadItem[] = [];
|
||||
|
|
|
@ -3,6 +3,7 @@ import assert from "minimalistic-assert";
|
|||
|
||||
import render_user_pill from "../templates/user_pill.hbs";
|
||||
|
||||
import {MAX_ITEMS} from "./bootstrap_typeahead";
|
||||
import * as common from "./common";
|
||||
import * as direct_message_group_data from "./direct_message_group_data";
|
||||
import {Filter, create_user_pill_context} from "./filter";
|
||||
|
@ -41,7 +42,7 @@ export type Suggestion = {
|
|||
}
|
||||
);
|
||||
|
||||
export const max_num_of_search_results = 12;
|
||||
export const max_num_of_search_results = MAX_ITEMS;
|
||||
|
||||
function channel_matches_query(channel_name: string, q: string): boolean {
|
||||
return common.phrase_match(q, channel_name);
|
||||
|
|
|
@ -169,7 +169,6 @@ function build_page(): void {
|
|||
language_labels = realm_playground.get_pygments_typeahead_list_for_settings(query);
|
||||
return [...language_labels.keys()];
|
||||
},
|
||||
items: 5,
|
||||
helpOnEmptyStrings: true,
|
||||
highlighter_html: (item: string): string =>
|
||||
render_typeahead_item({primary: language_labels.get(item)}),
|
||||
|
|
|
@ -6,6 +6,7 @@ import * as typeahead from "../shared/src/typeahead";
|
|||
import type {EmojiSuggestion} from "../shared/src/typeahead";
|
||||
import render_typeahead_list_item from "../templates/typeahead_list_item.hbs";
|
||||
|
||||
import {MAX_ITEMS} from "./bootstrap_typeahead";
|
||||
import * as buddy_data from "./buddy_data";
|
||||
import * as compose_state from "./compose_state";
|
||||
import type {LanguageSuggestion, SlashCommandSuggestion} from "./composebox_typeahead";
|
||||
|
@ -416,7 +417,7 @@ export function sort_recipients<UserType extends UserOrMentionPillData | UserPil
|
|||
current_stream_id,
|
||||
current_topic,
|
||||
groups = [],
|
||||
max_num_items = 20,
|
||||
max_num_items = MAX_ITEMS,
|
||||
}: {
|
||||
users: UserType[];
|
||||
query: string;
|
||||
|
|
|
@ -1404,6 +1404,12 @@ textarea.new_message_textarea {
|
|||
.typeahead-menu {
|
||||
list-style: none;
|
||||
margin: 4px 0;
|
||||
max-height: min(248px, 95vh);
|
||||
overflow-y: auto;
|
||||
|
||||
.simplebar-content {
|
||||
min-width: max-content;
|
||||
}
|
||||
}
|
||||
|
||||
.typeahead-header {
|
||||
|
|
|
@ -166,7 +166,7 @@
|
|||
color: var(--color-text-search-hover);
|
||||
}
|
||||
|
||||
.typeahead-menu > li > a {
|
||||
.typeahead-menu .simplebar-content > li > a {
|
||||
max-width: none;
|
||||
}
|
||||
}
|
||||
|
@ -331,7 +331,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
.typeahead-menu > li > a {
|
||||
.typeahead-menu .simplebar-content > li > a {
|
||||
padding: 3px 30px;
|
||||
/* Override white-space: nowrap from zulip.css */
|
||||
white-space: normal;
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
z-index: 1051;
|
||||
}
|
||||
|
||||
.typeahead.dropdown-menu .typeahead-menu {
|
||||
.typeahead.dropdown-menu .typeahead-menu .simplebar-content {
|
||||
& > li {
|
||||
word-break: break-word;
|
||||
|
||||
|
|
|
@ -59,11 +59,6 @@ const settings_config = zrequire("settings_config");
|
|||
|
||||
const ct = composebox_typeahead;
|
||||
|
||||
// Use a slightly larger value than what's user-facing
|
||||
// to facilitate testing different combinations of
|
||||
// broadcast-mentions/persons/groups.
|
||||
ct.__Rewire__("max_num_items", 15);
|
||||
|
||||
function user_item(user) {
|
||||
return {type: "user", user};
|
||||
}
|
||||
|
|
|
@ -162,7 +162,6 @@ run_test("set_up_user", ({mock_template, override, override_rewire}) => {
|
|||
|
||||
override(bootstrap_typeahead, "Typeahead", (input_element, config) => {
|
||||
assert.equal(input_element.$element, $fake_input);
|
||||
assert.equal(config.items, 5);
|
||||
assert.ok(config.dropup);
|
||||
assert.ok(config.stopAdvance);
|
||||
|
||||
|
@ -255,7 +254,6 @@ run_test("set_up_stream", ({mock_template, override, override_rewire}) => {
|
|||
|
||||
override(bootstrap_typeahead, "Typeahead", (input_element, config) => {
|
||||
assert.equal(input_element.$element, $fake_input);
|
||||
assert.equal(config.items, 12);
|
||||
assert.ok(config.dropup);
|
||||
assert.ok(config.stopAdvance);
|
||||
|
||||
|
@ -345,7 +343,6 @@ run_test("set_up_combined", ({mock_template, override, override_rewire}) => {
|
|||
let opts = {};
|
||||
override(bootstrap_typeahead, "Typeahead", (input_element, config) => {
|
||||
assert.equal(input_element.$element, $fake_input);
|
||||
assert.equal(config.items, 5);
|
||||
assert.ok(config.dropup);
|
||||
assert.ok(config.stopAdvance);
|
||||
|
||||
|
|
|
@ -16,8 +16,6 @@ const stream_topic_history = zrequire("stream_topic_history");
|
|||
const people = zrequire("people");
|
||||
const search = zrequire("search_suggestion");
|
||||
|
||||
search.__Rewire__("max_num_of_search_results", 15);
|
||||
|
||||
const me = {
|
||||
email: "myself@zulip.com",
|
||||
full_name: "Me Myself",
|
||||
|
|
Loading…
Reference in New Issue