mirror of https://github.com/zulip/zulip.git
dropdown_widget: Refactor to use Class.
This commit is contained in:
parent
d91d6d1fd1
commit
6efcb7a349
|
@ -210,7 +210,10 @@ export async function check_compose_state(
|
|||
const form_params: Record<string, string> = {content: params.content};
|
||||
if (params.stream) {
|
||||
assert.equal(
|
||||
await get_text_from_selector(page, "#compose_select_recipient_name"),
|
||||
await get_text_from_selector(
|
||||
page,
|
||||
"#compose_select_recipient_widget .dropdown_widget_value",
|
||||
),
|
||||
params.stream,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -709,7 +709,7 @@ export function initialize() {
|
|||
}
|
||||
|
||||
// The dropdown menu needs to process clicks to open and close.
|
||||
if ($target.parents("#compose_recipient_selection_dropdown").length > 0) {
|
||||
if ($target.parents("#compose_select_recipient_widget_wrapper").length > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -225,7 +225,7 @@ export function start(msg_type, opts) {
|
|||
}
|
||||
|
||||
const $stream_header_colorblock = $(
|
||||
"#compose_recipient_selection_dropdown .stream_header_colorblock",
|
||||
"#compose_select_recipient_widget_wrapper .stream_header_colorblock",
|
||||
);
|
||||
stream_bar.decorate(opts.stream, $stream_header_colorblock);
|
||||
|
||||
|
|
|
@ -165,9 +165,11 @@ function switch_message_type(message_type) {
|
|||
function update_recipient_label(stream_name) {
|
||||
const stream = stream_data.get_sub_by_name(stream_name);
|
||||
if (stream === undefined) {
|
||||
$("#compose_select_recipient_name").text($t({defaultMessage: "Select a stream"}));
|
||||
$("#compose_select_recipient_widget .dropdown_widget_value").text(
|
||||
$t({defaultMessage: "Select a stream"}),
|
||||
);
|
||||
} else {
|
||||
$("#compose_select_recipient_name").html(
|
||||
$("#compose_select_recipient_widget .dropdown_widget_value").html(
|
||||
render_inline_decorated_stream_name({stream, show_colored_icon: true}),
|
||||
);
|
||||
}
|
||||
|
@ -192,7 +194,7 @@ export function update_compose_for_message_type(message_type, opts) {
|
|||
// the "DM" button display string so we wouldn't have to manually change
|
||||
// it here.
|
||||
const direct_message_label = $t({defaultMessage: "DM"});
|
||||
$("#compose_select_recipient_name").html(
|
||||
$("#compose_select_recipient_widget .dropdown_widget_value").html(
|
||||
`<i class="zulip-icon zulip-icon-users stream-privacy-type-icon"></i> ${direct_message_label}`,
|
||||
);
|
||||
}
|
||||
|
@ -216,7 +218,7 @@ export function on_compose_select_recipient_update() {
|
|||
if (curr_message_type === "stream") {
|
||||
// Update stream name in the recipient box.
|
||||
const $stream_header_colorblock = $(
|
||||
"#compose_recipient_selection_dropdown .stream_header_colorblock",
|
||||
"#compose_select_recipient_widget_wrapper .stream_header_colorblock",
|
||||
);
|
||||
const stream_name = compose_state.stream_name();
|
||||
update_recipient_label(stream_name);
|
||||
|
@ -270,7 +272,7 @@ function compose_recipient_dropdown_on_show(dropdown) {
|
|||
const window_height = window.innerHeight;
|
||||
const search_box_and_padding_height = 50;
|
||||
// pixels above compose box.
|
||||
const recipient_input_top = $("#compose_recipient_selection_dropdown").get_offset_to_window()
|
||||
const recipient_input_top = $("#compose_select_recipient_widget_wrapper").get_offset_to_window()
|
||||
.top;
|
||||
const top_space = recipient_input_top - top_offset - search_box_and_padding_height;
|
||||
// pixels below compose starting from top of compose box.
|
||||
|
@ -296,7 +298,7 @@ export function open_compose_recipient_dropdown() {
|
|||
}
|
||||
|
||||
function focus_compose_recipient() {
|
||||
$("#compose_recipient_selection_dropdown").trigger("focus");
|
||||
$("#compose_select_recipient_widget_wrapper").trigger("focus");
|
||||
}
|
||||
|
||||
// NOTE: Since tippy triggers this on `mousedown` it is always triggered before say a `click` on `textarea`.
|
||||
|
@ -316,28 +318,17 @@ function on_hidden_callback() {
|
|||
}
|
||||
|
||||
export function initialize() {
|
||||
dropdown_widget.setup(
|
||||
{
|
||||
target: "#compose_select_recipient_widget",
|
||||
},
|
||||
get_options_for_recipient_widget,
|
||||
new dropdown_widget.DropdownWidget({
|
||||
widget_name: "compose_select_recipient",
|
||||
get_options: get_options_for_recipient_widget,
|
||||
item_click_callback,
|
||||
{
|
||||
on_show_callback: compose_recipient_dropdown_on_show,
|
||||
on_exit_with_escape_callback: focus_compose_recipient,
|
||||
// We want to focus on topic box if dropdown was closed via selecting an item.
|
||||
focus_target_on_hidden: false,
|
||||
on_hidden_callback,
|
||||
},
|
||||
);
|
||||
|
||||
$("#compose_recipient_selection_dropdown").on("keydown", (e) => {
|
||||
if (e.key === "Enter") {
|
||||
open_compose_recipient_dropdown();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
$events_container: $("body"),
|
||||
on_show_callback: compose_recipient_dropdown_on_show,
|
||||
on_exit_with_escape_callback: focus_compose_recipient,
|
||||
// We want to focus on topic box if dropdown was closed via selecting an item.
|
||||
focus_target_on_hidden: false,
|
||||
on_hidden_callback,
|
||||
}).setup();
|
||||
|
||||
// `keyup` isn't relevant for streams since it registers as a change only
|
||||
// when an item in the dropdown is selected.
|
||||
|
|
|
@ -162,7 +162,7 @@ export function focus_in_empty_compose(consider_start_of_whitespace_message_empt
|
|||
return private_message_recipient().length === 0;
|
||||
case "stream_message_recipient_topic":
|
||||
return topic() === "";
|
||||
case "compose_select_recipient_name":
|
||||
case "compose_select_recipient_widget_wrapper":
|
||||
return stream_name() === "";
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,7 @@ function get_focus_area(msg_type, opts) {
|
|||
}
|
||||
|
||||
if (msg_type === "stream") {
|
||||
return "#compose_select_recipient_widget";
|
||||
return "#compose_select_recipient_widget_wrapper";
|
||||
}
|
||||
return "#private_message_recipient";
|
||||
}
|
||||
|
|
|
@ -436,7 +436,7 @@ export function validation_error(error_type, stream_name) {
|
|||
$t({defaultMessage: "Error checking subscription."}),
|
||||
compose_banner.CLASSNAMES.subscription_error,
|
||||
$banner_container,
|
||||
$("#compose_select_recipient_widget"),
|
||||
$("#compose_select_recipient_widget_wrapper"),
|
||||
);
|
||||
return false;
|
||||
case "not-subscribed": {
|
||||
|
@ -485,7 +485,7 @@ function validate_stream_message(scheduling_message) {
|
|||
$t({defaultMessage: "Please specify a stream."}),
|
||||
compose_banner.CLASSNAMES.missing_stream,
|
||||
$banner_container,
|
||||
$("#compose_select_recipient_widget"),
|
||||
$("#compose_select_recipient_widget_wrapper"),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -159,6 +159,8 @@ export function launch(conf: DialogWidgetConfig): void {
|
|||
if ($(this).is("input[type='file']") && $(this).prop("files")?.length) {
|
||||
// If the input is a file input and a file has been selected, set value to file object
|
||||
current_values[property_name] = $(this).prop("files")[0];
|
||||
} else if (property_name === "edit_bot_owner") {
|
||||
current_values[property_name] = $(this).find(".dropdown_widget_value").text();
|
||||
} else {
|
||||
current_values[property_name] = $(this).val();
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import $ from "jquery";
|
||||
import * as tippy from "tippy.js";
|
||||
|
||||
import render_dropdown_disabled_state from "../templates/dropdown_disabled_state.hbs";
|
||||
import render_dropdown_list from "../templates/dropdown_list.hbs";
|
||||
import render_dropdown_list_container from "../templates/dropdown_list_container.hbs";
|
||||
import render_inline_decorated_stream_name from "../templates/inline_decorated_stream_name.hbs";
|
||||
|
||||
import * as blueslip from "./blueslip";
|
||||
import * as ListWidget from "./list_widget";
|
||||
import {default_popover_props} from "./popover_menus";
|
||||
import {parse_html} from "./ui_util";
|
||||
|
@ -11,166 +14,258 @@ import {parse_html} from "./ui_util";
|
|||
/* Sync with max-height set in zulip.css */
|
||||
export const DEFAULT_DROPDOWN_HEIGHT = 210;
|
||||
const noop = () => {};
|
||||
export const DATA_TYPES = {
|
||||
NUMBER: "number",
|
||||
STRING: "string",
|
||||
};
|
||||
|
||||
export function setup(tippy_props, get_options, item_click_callback, dropdown_props = {}) {
|
||||
// Define all possible `dropdown_props` here so that they are easy to track.
|
||||
const on_show_callback = dropdown_props.on_show_callback || noop;
|
||||
const on_hidden_callback = dropdown_props.on_hidden_callback || noop;
|
||||
const on_exit_with_escape_callback = dropdown_props.on_exit_with_escape_callback || noop;
|
||||
// Used to focus the `target` after dropdown is closed. This is important since the dropdown is
|
||||
// appended to `body` and hence `body` is focused when the dropdown is closed, which makes
|
||||
// it hard for the user to get focus back to the `target`.
|
||||
const focus_target_on_hidden = dropdown_props.focus_target_on_hidden || true;
|
||||
// Should enter keypress on target show the dropdown.
|
||||
const show_on_target_enter_keypress = dropdown_props.show_on_target_enter_keypress || false;
|
||||
export class DropdownWidget {
|
||||
constructor({
|
||||
widget_name,
|
||||
get_options,
|
||||
item_click_callback,
|
||||
// Provide an parent element to widget which will be re-rendered if the widget is setup again.
|
||||
// It is important to not pass `$("body")` here for widgets that would be `setup()`
|
||||
// multiple times, so that we don't have duplicate event handlers.
|
||||
$events_container,
|
||||
on_show_callback = noop,
|
||||
on_mount_callback = noop,
|
||||
on_hidden_callback = noop,
|
||||
on_exit_with_escape_callback = noop,
|
||||
render_selected_option = noop,
|
||||
// Used to focus the `target` after dropdown is closed. This is important since the dropdown is
|
||||
// appended to `body` and hence `body` is focused when the dropdown is closed, which makes
|
||||
// it hard for the user to get focus back to the `target`.
|
||||
focus_target_on_hidden = true,
|
||||
tippy_props = {},
|
||||
// NOTE: Any value other than `null` will be rendered when class is initialized.
|
||||
default_id = null,
|
||||
unique_id_type = null,
|
||||
}) {
|
||||
this.widget_name = widget_name;
|
||||
this.widget_id = `#${CSS.escape(widget_name)}_widget`;
|
||||
// A widget wrapper may not exist based on the UI requirement.
|
||||
this.widget_wrapper_id = `${this.widget_id}_wrapper`;
|
||||
this.widget_value_selector = `${this.widget_id} .dropdown_widget_value`;
|
||||
this.get_options = get_options;
|
||||
this.item_click_callback = item_click_callback;
|
||||
this.focus_target_on_hidden = focus_target_on_hidden;
|
||||
this.on_show_callback = on_show_callback;
|
||||
this.on_mount_callback = on_mount_callback;
|
||||
this.on_hidden_callback = on_hidden_callback;
|
||||
this.on_exit_with_escape_callback = on_exit_with_escape_callback;
|
||||
this.render_selected_option = render_selected_option;
|
||||
this.tippy_props = tippy_props;
|
||||
this.list_widget = null;
|
||||
this.instance = null;
|
||||
this.default_id = default_id;
|
||||
this.current_value = default_id;
|
||||
this.unique_id_type = unique_id_type;
|
||||
this.$events_container = $events_container;
|
||||
}
|
||||
|
||||
if (show_on_target_enter_keypress) {
|
||||
$("body").on("keypress", tippy_props.target, (e) => {
|
||||
if (e.key === "Enter") {
|
||||
$(tippy_props.target).trigger("click");
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
init() {
|
||||
if (this.current_value !== null) {
|
||||
this.render();
|
||||
}
|
||||
|
||||
this.$events_container.on(
|
||||
"keydown",
|
||||
`${this.widget_id}, ${this.widget_wrapper_id}`,
|
||||
(e) => {
|
||||
if (e.key === "Enter") {
|
||||
$(`${this.widget_id}`).trigger("click");
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
setup() {
|
||||
this.init();
|
||||
const delegate_container = this.$events_container.get(0);
|
||||
if (!delegate_container) {
|
||||
blueslip.error(
|
||||
"Cannot initialize dropdown. `$events_container` empty.",
|
||||
this.$events_container,
|
||||
);
|
||||
}
|
||||
this.instance = tippy.delegate(delegate_container, {
|
||||
...default_popover_props,
|
||||
target: this.widget_id,
|
||||
// Custom theme defined in popovers.css
|
||||
theme: "dropdown-widget",
|
||||
arrow: false,
|
||||
onShow: function (instance) {
|
||||
instance.setContent(parse_html(render_dropdown_list_container()));
|
||||
const $popper = $(instance.popper);
|
||||
const $dropdown_list_body = $popper.find(".dropdown-list");
|
||||
const $search_input = $popper.find(".dropdown-list-search-input");
|
||||
|
||||
this.list_widget = ListWidget.create($dropdown_list_body, this.get_options(), {
|
||||
name: `${CSS.escape(this.widget_name)}-list-widget`,
|
||||
get_item: ListWidget.default_get_item,
|
||||
modifier(item) {
|
||||
return render_dropdown_list({item});
|
||||
},
|
||||
filter: {
|
||||
$element: $search_input,
|
||||
predicate(item, value) {
|
||||
return item.name.toLowerCase().includes(value);
|
||||
},
|
||||
},
|
||||
$simplebar_container: $popper.find(".dropdown-list-wrapper"),
|
||||
});
|
||||
|
||||
$search_input.on("input.list_widget_filter", () => {
|
||||
const list_items = this.list_widget.get_current_list();
|
||||
const $no_search_results = $popper.find(".no-dropdown-items");
|
||||
if (list_items.length === 0) {
|
||||
$no_search_results.show();
|
||||
} else {
|
||||
$no_search_results.hide();
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard handler
|
||||
$popper.on("keydown", (e) => {
|
||||
function trigger_element_focus($element) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// When bringing a non-visible element into view, scroll as minimum as possible.
|
||||
$element[0]?.scrollIntoView({block: "nearest"});
|
||||
$element.trigger("focus");
|
||||
}
|
||||
|
||||
const $search_input = $popper.find(".dropdown-list-search-input");
|
||||
const list_items = this.list_widget.get_current_list();
|
||||
if (list_items.length === 0 && !(e.key === "Escape")) {
|
||||
// Let the browser handle it.
|
||||
return;
|
||||
}
|
||||
|
||||
function first_item() {
|
||||
const first_item = list_items[0];
|
||||
return $popper.find(`.list-item[data-unique-id="${first_item.unique_id}"]`);
|
||||
}
|
||||
|
||||
function last_item() {
|
||||
const last_item = list_items.at(-1);
|
||||
return $popper.find(`.list-item[data-unique-id="${last_item.unique_id}"]`);
|
||||
}
|
||||
|
||||
const render_all_items_and_focus_last_item = function () {
|
||||
// List widget doesn't render all items by default, so we need to render all
|
||||
// the items and focus on the last element.
|
||||
const list_items = this.list_widget.get_current_list();
|
||||
this.list_widget.render(list_items.length);
|
||||
trigger_element_focus(last_item());
|
||||
}.bind(this);
|
||||
|
||||
switch (e.key) {
|
||||
case "Enter":
|
||||
if (e.target === $search_input.get(0)) {
|
||||
// Select first item if in search input.
|
||||
first_item().trigger("click");
|
||||
} else if (list_items.length !== 0) {
|
||||
$(e.target).trigger("click");
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
break;
|
||||
|
||||
case "Escape":
|
||||
instance.hide();
|
||||
this.on_exit_with_escape_callback();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
break;
|
||||
|
||||
case "Tab":
|
||||
case "ArrowDown":
|
||||
switch (e.target) {
|
||||
case last_item().get(0):
|
||||
trigger_element_focus($search_input);
|
||||
break;
|
||||
case $search_input.get(0):
|
||||
trigger_element_focus(first_item());
|
||||
break;
|
||||
default:
|
||||
trigger_element_focus($(e.target).next());
|
||||
}
|
||||
break;
|
||||
|
||||
case "ArrowUp":
|
||||
switch (e.target) {
|
||||
case first_item().get(0):
|
||||
trigger_element_focus($search_input);
|
||||
break;
|
||||
case $search_input.get(0):
|
||||
render_all_items_and_focus_last_item();
|
||||
break;
|
||||
default:
|
||||
trigger_element_focus($(e.target).prev());
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Click on item.
|
||||
$popper.one("click", ".list-item", (event) => {
|
||||
this.current_value = $(event.currentTarget).attr("data-unique-id");
|
||||
if (this.unique_id_type === DATA_TYPES.NUMBER) {
|
||||
this.current_value = Number.parseInt(this.current_value, 10);
|
||||
}
|
||||
this.item_click_callback(event, instance);
|
||||
});
|
||||
|
||||
// Set focus on search input when dropdown opens.
|
||||
setTimeout(() => {
|
||||
$(".dropdown-list-search-input").trigger("focus");
|
||||
});
|
||||
|
||||
this.on_show_callback(instance);
|
||||
}.bind(this),
|
||||
onMount: function (instance) {
|
||||
this.on_mount_callback(instance);
|
||||
}.bind(this),
|
||||
onHidden: function (instance) {
|
||||
if (this.focus_target_on_hidden) {
|
||||
$(this.widget_id).trigger("focus");
|
||||
}
|
||||
this.on_hidden_callback(instance);
|
||||
this.instance = null;
|
||||
}.bind(this),
|
||||
...this.tippy_props,
|
||||
});
|
||||
}
|
||||
|
||||
tippy.delegate("body", {
|
||||
...default_popover_props,
|
||||
// Custom theme defined in popovers.css
|
||||
theme: "dropdown-widget",
|
||||
arrow: false,
|
||||
onShow(instance) {
|
||||
instance.setContent(parse_html(render_dropdown_list_container()));
|
||||
const $popper = $(instance.popper);
|
||||
const $dropdown_list_body = $popper.find(".dropdown-list");
|
||||
const $search_input = $popper.find(".dropdown-list-search-input");
|
||||
value() {
|
||||
return this.current_value;
|
||||
}
|
||||
|
||||
const list_widget = ListWidget.create($dropdown_list_body, get_options(), {
|
||||
name: `${CSS.escape(tippy_props.target)}-list-widget`,
|
||||
get_item: ListWidget.default_get_item,
|
||||
modifier(item) {
|
||||
return render_dropdown_list({item});
|
||||
},
|
||||
filter: {
|
||||
$element: $search_input,
|
||||
predicate(item, value) {
|
||||
return item.name.toLowerCase().includes(value);
|
||||
},
|
||||
},
|
||||
$simplebar_container: $popper.find(".dropdown-list-wrapper"),
|
||||
});
|
||||
// NOTE: This function needs to be explicitly called when you want to update the
|
||||
// current value of the widget. We don't call this automatically since some of our
|
||||
// dropdowns don't need it. Maybe we can follow a reverse approach in the future.
|
||||
render(value) {
|
||||
// Check if the value is valid otherwise just render previous value.
|
||||
if (typeof value === typeof this.current_value) {
|
||||
this.current_value = value;
|
||||
}
|
||||
|
||||
$search_input.on("input.list_widget_filter", () => {
|
||||
const list_items = list_widget.get_current_list();
|
||||
const $no_search_results = $popper.find(".no-dropdown-items");
|
||||
if (list_items.length === 0) {
|
||||
$no_search_results.show();
|
||||
} else {
|
||||
$no_search_results.hide();
|
||||
}
|
||||
});
|
||||
|
||||
// Keyboard handler
|
||||
$popper.on("keydown", (e) => {
|
||||
function trigger_element_focus($element) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
// When brining a non-visible element into view, scroll as minimum as possible.
|
||||
$element[0]?.scrollIntoView({block: "nearest"});
|
||||
$element.trigger("focus");
|
||||
}
|
||||
|
||||
const $search_input = $popper.find(".dropdown-list-search-input");
|
||||
const list_items = list_widget.get_current_list();
|
||||
if (list_items.length === 0 && !(e.key === "Escape")) {
|
||||
// Let the browser handle it.
|
||||
return;
|
||||
}
|
||||
|
||||
function first_item() {
|
||||
const first_item = list_items[0];
|
||||
return $popper.find(`.list-item[data-unique-id="${first_item.unique_id}"]`);
|
||||
}
|
||||
|
||||
function last_item() {
|
||||
const last_item = list_items.at(-1);
|
||||
return $popper.find(`.list-item[data-unique-id="${last_item.unique_id}"]`);
|
||||
}
|
||||
|
||||
function render_all_items_and_focus_last_item() {
|
||||
// List widget doesn't render all items by default, so we need to render all
|
||||
// the items and focus on the last element.
|
||||
const list_items = list_widget.get_current_list();
|
||||
list_widget.render(list_items.length);
|
||||
trigger_element_focus(last_item());
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
case "Enter":
|
||||
if (e.target === $search_input.get(0)) {
|
||||
// Select first item if in search input.
|
||||
first_item().trigger("click");
|
||||
} else if (list_items.length !== 0) {
|
||||
$(e.target).trigger("click");
|
||||
}
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
break;
|
||||
|
||||
case "Escape":
|
||||
instance.hide();
|
||||
on_exit_with_escape_callback();
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
break;
|
||||
|
||||
case "Tab":
|
||||
case "ArrowDown":
|
||||
switch (e.target) {
|
||||
case last_item().get(0):
|
||||
trigger_element_focus($search_input);
|
||||
break;
|
||||
case $search_input.get(0):
|
||||
trigger_element_focus(first_item());
|
||||
break;
|
||||
default:
|
||||
trigger_element_focus($(e.target).next());
|
||||
}
|
||||
break;
|
||||
|
||||
case "ArrowUp":
|
||||
switch (e.target) {
|
||||
case first_item().get(0):
|
||||
trigger_element_focus($search_input);
|
||||
break;
|
||||
case $search_input.get(0):
|
||||
render_all_items_and_focus_last_item();
|
||||
break;
|
||||
default:
|
||||
trigger_element_focus($(e.target).prev());
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
// Click on item.
|
||||
$popper.one("click", ".list-item", (event) => {
|
||||
item_click_callback(event, instance);
|
||||
});
|
||||
|
||||
// Set focus on search input when dropdown opens.
|
||||
setTimeout(() => {
|
||||
$(".dropdown-list-search-input").trigger("focus");
|
||||
});
|
||||
|
||||
on_show_callback(instance);
|
||||
},
|
||||
onHidden(instance) {
|
||||
if (focus_target_on_hidden) {
|
||||
$(tippy_props.target).trigger("focus");
|
||||
}
|
||||
on_hidden_callback(instance);
|
||||
},
|
||||
...tippy_props,
|
||||
});
|
||||
const option = this.get_options().find((option) => option.unique_id === this.current_value);
|
||||
if (option.is_setting_disabled) {
|
||||
$(this.widget_value_selector).html(render_dropdown_disabled_state({name: option.name}));
|
||||
} else if (option.stream) {
|
||||
$(this.widget_value_selector).html(
|
||||
render_inline_decorated_stream_name({
|
||||
stream: option.stream,
|
||||
show_colored_icon: true,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
$(this.widget_value_selector).text(option.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import * as user_profile from "./user_profile";
|
|||
|
||||
const OUTGOING_WEBHOOK_BOT_TYPE = "3";
|
||||
const EMBEDDED_BOT_TYPE = "4";
|
||||
export let bot_owner_dropdown_widget;
|
||||
|
||||
const focus_tab = {
|
||||
active_bots_tab() {
|
||||
|
@ -371,9 +372,7 @@ export function show_edit_bot_info_modal(user_id, from_user_info_popover) {
|
|||
formData.append("csrfmiddlewaretoken", csrf_token);
|
||||
formData.append("full_name", $full_name.val());
|
||||
formData.append("role", JSON.stringify(role));
|
||||
const new_bot_owner_id = $("#bot_owner_dropdown_widget .bot_owner_name").attr(
|
||||
"data-user-id",
|
||||
);
|
||||
const new_bot_owner_id = bot_owner_dropdown_widget.value();
|
||||
if (new_bot_owner_id) {
|
||||
formData.append("bot_owner_id", new_bot_owner_id);
|
||||
}
|
||||
|
@ -424,29 +423,26 @@ export function show_edit_bot_info_modal(user_id, from_user_info_popover) {
|
|||
}
|
||||
|
||||
function item_click_callback(event, dropdown) {
|
||||
const $user = $(event.currentTarget);
|
||||
const user_full_name = $user.attr("data-name");
|
||||
const new_bot_owner_id = Number.parseInt($user.attr("data-unique-id"), 10);
|
||||
const $bot_owner = $("#bot_owner_dropdown_widget .bot_owner_name");
|
||||
$bot_owner.text(user_full_name);
|
||||
$bot_owner.attr("data-user-id", new_bot_owner_id);
|
||||
$("#edit_bot_modal .bot_owner_id").val(new_bot_owner_id).trigger("input");
|
||||
bot_owner_dropdown_widget.render();
|
||||
// Let dialog_wigdet know that there was a change in value.
|
||||
$(bot_owner_dropdown_widget.widget_id).trigger("input");
|
||||
dropdown.hide();
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
}
|
||||
|
||||
dropdown_widget.setup(
|
||||
{
|
||||
target: "#bot_owner_dropdown_widget",
|
||||
placement: "bottom-start",
|
||||
},
|
||||
bot_owner_dropdown_widget = new dropdown_widget.DropdownWidget({
|
||||
widget_name: "edit_bot_owner",
|
||||
get_options,
|
||||
item_click_callback,
|
||||
{
|
||||
show_on_target_enter_keypress: true,
|
||||
$events_container: $("#bot-edit-form"),
|
||||
tippy_props: {
|
||||
placement: "bottom-start",
|
||||
},
|
||||
);
|
||||
default_id: owner_id,
|
||||
unique_id_type: dropdown_widget.DATA_TYPES.NUMBER,
|
||||
});
|
||||
bot_owner_dropdown_widget.setup();
|
||||
|
||||
$("#bot-role-select").val(bot.role);
|
||||
if (!page_params.is_owner) {
|
||||
|
|
|
@ -30,7 +30,7 @@ function add_choice_row($widget) {
|
|||
function get_chosen_default_streams() {
|
||||
// Return the set of stream id's of streams chosen in the default stream modal.
|
||||
return new Set(
|
||||
$("#default-stream-choices .choice-row .stream_name")
|
||||
$("#default-stream-choices .choice-row .dropdown_widget_value")
|
||||
.map((_i, elem) => $(elem).data("stream-id")?.toString())
|
||||
.get(),
|
||||
);
|
||||
|
@ -39,7 +39,7 @@ function get_chosen_default_streams() {
|
|||
function create_choice_row() {
|
||||
const $container = $("#default-stream-choices");
|
||||
const value = settings_profile_fields.get_value_for_new_option("#default-stream-choices");
|
||||
const stream_dropdown_widget_name = `select_default_stream_${value}_widget`;
|
||||
const stream_dropdown_widget_name = `select_default_stream_${value}`;
|
||||
const row = render_default_stream_choice({value, stream_dropdown_widget_name});
|
||||
$container.append(row);
|
||||
|
||||
|
@ -57,8 +57,8 @@ function create_choice_row() {
|
|||
const selected_stream_name = $selected_stream.attr("data-name");
|
||||
const selected_stream_id = Number.parseInt($selected_stream.data("unique-id"), 10);
|
||||
|
||||
const $stream_dropdown_widget = $(`#${CSS.escape(stream_dropdown_widget_name)}`);
|
||||
const $stream_name = $stream_dropdown_widget.find(".stream_name");
|
||||
const $stream_dropdown_widget = $(`#${CSS.escape(stream_dropdown_widget_name)}_widget`);
|
||||
const $stream_name = $stream_dropdown_widget.find(".dropdown_widget_value");
|
||||
$stream_name.text(selected_stream_name);
|
||||
$stream_name.data("stream-id", selected_stream_id);
|
||||
|
||||
|
@ -69,17 +69,15 @@ function create_choice_row() {
|
|||
event.preventDefault();
|
||||
}
|
||||
|
||||
dropdown_widget.setup(
|
||||
{
|
||||
target: `#${stream_dropdown_widget_name}`,
|
||||
placement: "bottom-start",
|
||||
},
|
||||
new dropdown_widget.DropdownWidget({
|
||||
widget_name: stream_dropdown_widget_name,
|
||||
get_options,
|
||||
item_click_callback,
|
||||
{
|
||||
show_on_target_enter_keypress: true,
|
||||
$events_container: $container,
|
||||
tippy_props: {
|
||||
placement: "bottom-start",
|
||||
},
|
||||
);
|
||||
}).setup();
|
||||
}
|
||||
|
||||
const meta = {
|
||||
|
|
|
@ -340,9 +340,11 @@ export function build_move_topic_to_stream_popover(
|
|||
stream_bar.decorate(stream_name, $stream_header_colorblock);
|
||||
const stream = stream_data.get_sub_by_name(stream_name);
|
||||
if (stream === undefined) {
|
||||
$("#move_topic_stream_name").text($t({defaultMessage: "Select a stream"}));
|
||||
$("#move_topic_to_stream_widget .dropdown_widget_value").text(
|
||||
$t({defaultMessage: "Select a stream"}),
|
||||
);
|
||||
} else {
|
||||
$("#move_topic_stream_name").html(
|
||||
$("#move_topic_to_stream_widget .dropdown_widget_value").html(
|
||||
render_inline_decorated_stream_name({stream, show_colored_icon: true}),
|
||||
);
|
||||
}
|
||||
|
@ -391,29 +393,24 @@ export function build_move_topic_to_stream_popover(
|
|||
}
|
||||
return stream_data.can_post_messages_in_stream(stream);
|
||||
});
|
||||
dropdown_widget.setup(
|
||||
{
|
||||
target: "#move_topic_stream_widget",
|
||||
|
||||
new dropdown_widget.DropdownWidget({
|
||||
widget_name: "move_topic_to_stream",
|
||||
get_options: streams_list_options,
|
||||
item_click_callback: move_topic_on_update,
|
||||
$events_container: $("#move_topic_modal"),
|
||||
tippy_props: {
|
||||
// Overlap dropdown search input with stream selection button.
|
||||
placement: "bottom-start",
|
||||
offset: [0, -30],
|
||||
},
|
||||
streams_list_options,
|
||||
move_topic_on_update,
|
||||
);
|
||||
}).setup();
|
||||
|
||||
render_selected_stream();
|
||||
$("#select_stream_widget .dropdown-toggle").prop("disabled", disable_stream_input);
|
||||
$("#move_topic_modal .move_messages_edit_topic").on("input", () => {
|
||||
update_submit_button_disabled_state(stream_widget_value);
|
||||
});
|
||||
$(".move-topic-dropdown").on("keydown", (e) => {
|
||||
if (e.key === "Enter") {
|
||||
$("#move_topic_stream_widget").trigger("click");
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function focus_on_move_modal_render() {
|
||||
|
|
|
@ -1023,6 +1023,7 @@ div.overlay {
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
outline: none;
|
||||
color: var(--color-text-default);
|
||||
|
||||
.fa-chevron-down {
|
||||
position: relative;
|
||||
|
@ -1030,6 +1031,15 @@ div.overlay {
|
|||
}
|
||||
}
|
||||
|
||||
.setting-disabled-option {
|
||||
color: hsl(38deg 46% 54%);
|
||||
|
||||
& i {
|
||||
/* Values set to match text alignment in stream dropdown. */
|
||||
padding: 0 5px 0 1px;
|
||||
}
|
||||
}
|
||||
|
||||
.filter_text_input {
|
||||
padding: 4px 6px;
|
||||
color: hsl(0deg 0% 33%);
|
||||
|
|
|
@ -805,7 +805,7 @@ input.recipient_box {
|
|||
margin: auto;
|
||||
}
|
||||
|
||||
#compose_recipient_selection_dropdown {
|
||||
#compose_select_recipient_widget_wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
height: var(--compose-recipient-box-min-height);
|
||||
|
@ -814,26 +814,7 @@ input.recipient_box {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.dropdown-toggle {
|
||||
border-radius: 0 4px 4px 0 !important;
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.dropdown-menu {
|
||||
/* The Bootstrap default of 160px wraps too early.
|
||||
TODO: Replace this with a max-width and natural scaling? */
|
||||
width: 200px;
|
||||
top: auto;
|
||||
left: -10px;
|
||||
}
|
||||
|
||||
.dropup .dropdown-menu {
|
||||
bottom: 100%;
|
||||
margin-bottom: 17px;
|
||||
}
|
||||
|
||||
#compose_select_recipient_name {
|
||||
.dropdown_widget_value {
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
@ -849,10 +830,6 @@ input.recipient_box {
|
|||
}
|
||||
}
|
||||
|
||||
.dropdown-list-body .list_item a {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.fa-chevron-down {
|
||||
padding-left: 5px;
|
||||
color: hsl(0deg 0% 58%);
|
||||
|
|
|
@ -852,7 +852,7 @@ ul {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
.move-topic-dropdown {
|
||||
#move_topic_to_stream_widget_wrapper {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
|
||||
|
@ -870,7 +870,7 @@ ul {
|
|||
margin: 0;
|
||||
}
|
||||
|
||||
#move_topic_stream_name {
|
||||
.dropdown_widget_value {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
|
|
@ -648,7 +648,6 @@ input[type="checkbox"] {
|
|||
|
||||
#add-default-stream-modal {
|
||||
.dropdown-widget-button {
|
||||
width: 116px;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
|
|
|
@ -1032,12 +1032,12 @@ div.settings-radio-input-parent {
|
|||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.dropdown-list-widget {
|
||||
.dropdown-toggle {
|
||||
margin-bottom: 10px;
|
||||
min-width: 325px;
|
||||
max-width: 100%;
|
||||
}
|
||||
.dropdown-widget-button {
|
||||
/* Match the margin with other input groups around. */
|
||||
margin-bottom: 20px;
|
||||
min-width: 325px;
|
||||
max-width: 100%;
|
||||
height: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -58,15 +58,8 @@
|
|||
<div class="topic-marker-container order-1">
|
||||
<a role="button" class="narrow_to_compose_recipients zulip-icon zulip-icon-arrow-left-circle" data-tooltip-template-id="narrow_to_compose_recipients_tooltip"></a>
|
||||
</div>
|
||||
<div id="compose_recipient_selection_dropdown" class="new-style" tabindex="0">
|
||||
<div class="stream_header_colorblock"></div>
|
||||
<button id="compose_select_recipient_widget" class="dropdown-widget-button" type="button" tabindex="-1">
|
||||
<span id="compose_select_recipient_name">
|
||||
{{t 'Select a stream'}}
|
||||
</span>
|
||||
<i class="fa fa-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
{{> dropdown_widget_with_stream_colorblock
|
||||
widget_name="compose_select_recipient"}}
|
||||
<div class="topic-marker-container">
|
||||
<i class="fa fa-angle-right" aria-hidden="true"></i>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<span class="setting-disabled-option">{{name}}</span>
|
|
@ -5,6 +5,8 @@
|
|||
{{> inline_decorated_stream_name stream=stream show_colored_icon=true}}
|
||||
{{else if is_direct_message}}
|
||||
<i class="zulip-icon zulip-icon-users stream-privacy-type-icon"></i> {{name}}
|
||||
{{else if is_setting_disabled}}
|
||||
<span class="setting-disabled-option"><i class="fa fa-ban" aria-hidden="true"></i>{{t "Disable" }}</span>
|
||||
{{else}}
|
||||
{{name}}
|
||||
{{/if}}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
<button id="{{widget_name}}_widget" class="dropdown-widget-button" type="button" {{#if disable_keyboard_focus}}tabindex="-1"{{/if}} name="{{widget_name}}">
|
||||
<span class="dropdown_widget_value">{{#if default_text}}{{default_text}}{{/if}}</span>
|
||||
<i class="fa fa-chevron-down"></i>
|
||||
</button>
|
|
@ -0,0 +1,5 @@
|
|||
<div class="input-group" id="{{widget_name}}_widget_container">
|
||||
<label class="dropdown-title" for="{{widget_name}}_widget">{{label}}</label>
|
||||
<span class="prop-element hide" id="id_{{widget_name}}" data-setting-widget-type="dropdown-list-widget" {{#if value_type}}data-setting-value-type="{{value_type}}"{{/if}}></span>
|
||||
{{> dropdown_widget}}
|
||||
</div>
|
|
@ -0,0 +1,4 @@
|
|||
<div id="{{widget_name}}_widget_wrapper" tabindex="0">
|
||||
<div class="stream_header_colorblock"></div>
|
||||
{{> dropdown_widget disable_keyboard_focus="true"}}
|
||||
</div>
|
|
@ -9,15 +9,7 @@
|
|||
{{/if}}
|
||||
<div class="topic_stream_edit_header">
|
||||
{{#unless only_topic_edit}}
|
||||
<div class="move-topic-dropdown" tabindex="0">
|
||||
<div class="stream_header_colorblock"></div>
|
||||
<button id="move_topic_stream_widget" class="dropdown-widget-button" type="button" tabindex="-1">
|
||||
<span id="move_topic_stream_name">
|
||||
{{t 'Select a stream'}}
|
||||
</span>
|
||||
<i class="fa fa-chevron-down"></i>
|
||||
</button>
|
||||
</div>
|
||||
{{> dropdown_widget_with_stream_colorblock widget_name="move_topic_to_stream"}}
|
||||
<i class="fa fa-angle-right" aria-hidden="true"></i>
|
||||
{{/unless}}
|
||||
<input name="new_topic_name" type="text" class="move_messages_edit_topic modal_text_input" autocomplete="off" value="{{topic_name}}" {{#if disable_topic_input}}disabled{{/if}} />
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
<div class="choice-row" data-value="{{value}}">
|
||||
<div id="{{stream_dropdown_widget_name}}" class="dropdown-widget-button" role="button" tabindex="0">
|
||||
<span class="stream_name">
|
||||
Select stream
|
||||
</span>
|
||||
<i class="fa fa-chevron-down"></i>
|
||||
</div>
|
||||
{{> ../dropdown_widget widget_name=stream_dropdown_widget_name default_text=(t 'Select stream')}}
|
||||
<button type="button" class="button rounded small delete-choice" title="{{t 'Delete' }}">
|
||||
<i class="fa fa-trash-o" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
|
|
@ -21,16 +21,10 @@
|
|||
{{> dropdown_options_widget option_values=user_role_values}}
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group edit_bot_owner_container">
|
||||
<label for="bot_owner_dropdown_widget">{{t "Owner" }}</label>
|
||||
<div id="bot_owner_dropdown_widget" class="dropdown-widget-button" role="button" tabindex="0">
|
||||
<input type="hidden" name="bot_owner_id" class="bot_owner_id" value="{{current_bot_owner}}"/>
|
||||
<span class="bot_owner_name">
|
||||
{{owner_full_name}}
|
||||
</span>
|
||||
<i class="fa fa-chevron-down"></i>
|
||||
</div>
|
||||
</div>
|
||||
{{> ../dropdown_widget_with_label
|
||||
widget_name="edit_bot_owner"
|
||||
label=(t 'Owner')}}
|
||||
|
||||
<div id="service_data">
|
||||
</div>
|
||||
<div class="input-group edit-avatar-section">
|
||||
|
|
|
@ -747,7 +747,7 @@ run_test("get_focus_area", () => {
|
|||
}),
|
||||
"#compose-textarea",
|
||||
);
|
||||
assert.equal(get_focus_area("stream", {}), "#compose_select_recipient_widget");
|
||||
assert.equal(get_focus_area("stream", {}), "#compose_select_recipient_widget_wrapper");
|
||||
assert.equal(get_focus_area("stream", {stream: "fun"}), "#stream_message_recipient_topic");
|
||||
assert.equal(get_focus_area("stream", {stream: "fun", topic: "more"}), "#compose-textarea");
|
||||
assert.equal(
|
||||
|
|
|
@ -3,9 +3,9 @@
|
|||
const $ = require("./zjquery");
|
||||
|
||||
exports.mock_stream_header_colorblock = () => {
|
||||
const $stream_selection_dropdown = $("#compose_recipient_selection_dropdown");
|
||||
const $stream_selection_dropdown = $("#compose_select_recipient_widget_wrapper");
|
||||
const $stream_header_colorblock = $(".stream_header_colorblock");
|
||||
$("#compose_recipient_selection_dropdown .stream_header_colorblock").css = () => {};
|
||||
$("#compose_select_recipient_widget_wrapper .stream_header_colorblock").css = () => {};
|
||||
$stream_selection_dropdown.set_find_results(
|
||||
".stream_header_colorblock",
|
||||
$stream_header_colorblock,
|
||||
|
|
Loading…
Reference in New Issue