mirror of https://github.com/zulip/zulip.git
stream_settings: Migrate to new DropdownWidget.
Also, remove old DropdownListWidget since it is no longer used.
This commit is contained in:
parent
aa8e94ca6d
commit
875d564f2d
|
@ -1,627 +0,0 @@
|
|||
import $ from "jquery";
|
||||
import _ from "lodash";
|
||||
import tippy from "tippy.js";
|
||||
|
||||
import render_inline_decorated_stream_name from "../templates/inline_decorated_stream_name.hbs";
|
||||
import render_dropdown_list from "../templates/settings/dropdown_list.hbs";
|
||||
|
||||
import * as blueslip from "./blueslip";
|
||||
import {$t} from "./i18n";
|
||||
import * as keydown_util from "./keydown_util";
|
||||
import * as ListWidget from "./list_widget";
|
||||
|
||||
export class DropdownListWidget {
|
||||
constructor({
|
||||
widget_name,
|
||||
data,
|
||||
default_text,
|
||||
render_text = (item_name) => item_name,
|
||||
null_value = null,
|
||||
include_current_item = true,
|
||||
value,
|
||||
on_update = () => {},
|
||||
}) {
|
||||
// Initializing values
|
||||
this.widget_name = widget_name;
|
||||
this.data = data;
|
||||
this.default_text = default_text;
|
||||
this.render_text = render_text;
|
||||
this.null_value = null_value;
|
||||
this.include_current_item = include_current_item;
|
||||
this.initial_value = value;
|
||||
this.on_update = on_update;
|
||||
this.list_widget = null;
|
||||
|
||||
this.container_id = `${widget_name}_widget`;
|
||||
this.value_id = `id_${widget_name}`;
|
||||
|
||||
if (value === undefined) {
|
||||
this.initial_value = null_value;
|
||||
blueslip.warn("dropdown-list-widget: Called without a default value; using null value");
|
||||
}
|
||||
}
|
||||
|
||||
render_default_text($elem) {
|
||||
$elem.text(this.default_text);
|
||||
$elem.addClass("text-warning");
|
||||
$elem.closest(".input-group").find(".dropdown_list_reset_button").hide();
|
||||
}
|
||||
|
||||
render(value) {
|
||||
$(`#${CSS.escape(this.container_id)} #${CSS.escape(this.value_id)}`).data("value", value);
|
||||
|
||||
const $elem = $(`#${CSS.escape(this.container_id)} #${CSS.escape(this.widget_name)}_name`);
|
||||
|
||||
if (!value || value === this.null_value) {
|
||||
this.render_default_text($elem);
|
||||
return;
|
||||
}
|
||||
|
||||
// Happy path
|
||||
const item = this.data.find((x) => x.value === value.toString());
|
||||
|
||||
if (item === undefined) {
|
||||
this.render_default_text($elem);
|
||||
return;
|
||||
}
|
||||
|
||||
if (item.stream !== undefined) {
|
||||
const stream = item.stream;
|
||||
const rendered_stream_name_with_privacy_symbol_html =
|
||||
render_inline_decorated_stream_name({stream, show_colored_icon: true});
|
||||
$elem.html(rendered_stream_name_with_privacy_symbol_html);
|
||||
} else {
|
||||
const text = this.render_text(item.name);
|
||||
$elem.text(text);
|
||||
}
|
||||
|
||||
$elem.removeClass("text-warning");
|
||||
$elem.closest(".input-group").find(".dropdown_list_reset_button").show();
|
||||
}
|
||||
|
||||
update(value, e) {
|
||||
this.render(value);
|
||||
this.on_update(value, e);
|
||||
}
|
||||
|
||||
register_event_handlers() {
|
||||
$(`#${CSS.escape(this.container_id)} .dropdown-list-body`).on(
|
||||
"click",
|
||||
".list_item",
|
||||
(e) => {
|
||||
const value = $(e.currentTarget).attr("data-value");
|
||||
this.update(value, e);
|
||||
},
|
||||
);
|
||||
$(`#${CSS.escape(this.container_id)} .dropdown_list_reset_button`).on("click", (e) => {
|
||||
this.update(this.null_value, e);
|
||||
e.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
get_data = (data) => {
|
||||
if (this.include_current_item) {
|
||||
return data;
|
||||
}
|
||||
return data.filter((x) => x.value !== this.value.toString());
|
||||
};
|
||||
|
||||
setup_dropdown_widget(data) {
|
||||
const $dropdown_list_body = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-list-body`,
|
||||
).expectOne();
|
||||
const $search_input = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-search > input[type=text]`,
|
||||
);
|
||||
|
||||
this.list_widget = ListWidget.create($dropdown_list_body, this.get_data(data), {
|
||||
name: `${CSS.escape(this.widget_name)}_list`,
|
||||
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: $(`#${CSS.escape(this.container_id)} .dropdown-list-wrapper`),
|
||||
});
|
||||
}
|
||||
|
||||
replace_data(data) {
|
||||
this.data = data;
|
||||
this.list_widget.replace_list_data(this.get_data(data));
|
||||
}
|
||||
|
||||
// Sets the focus to the ListWidget input once the dropdown button is clicked.
|
||||
dropdown_toggle_click_handler() {
|
||||
const $dropdown_toggle = $(`#${CSS.escape(this.container_id)} .dropdown-toggle`);
|
||||
const $search_input = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-search > input[type=text]`,
|
||||
);
|
||||
|
||||
$dropdown_toggle.on("click", () => {
|
||||
$search_input.val("").trigger("input");
|
||||
});
|
||||
}
|
||||
|
||||
dropdown_keyboard_events() {
|
||||
const $search_input = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-search > input[type=text]`,
|
||||
);
|
||||
const $dropdown_menu = $(`.${CSS.escape(this.widget_name)}_setting .dropdown-menu`);
|
||||
|
||||
const dropdown_elements = () => {
|
||||
const $dropdown_list_body = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-list-body`,
|
||||
).expectOne();
|
||||
|
||||
return $dropdown_list_body.children().find("a");
|
||||
};
|
||||
|
||||
// Rest of the key handlers are supported by our
|
||||
// bootstrap library.
|
||||
$dropdown_menu.on("keydown", (e) => {
|
||||
function trigger_element_focus($element) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$element.trigger("focus");
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
case "ArrowDown": {
|
||||
switch (e.target) {
|
||||
case dropdown_elements().last()[0]:
|
||||
trigger_element_focus($search_input);
|
||||
break;
|
||||
case $search_input[0]:
|
||||
trigger_element_focus(dropdown_elements().first());
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "ArrowUp": {
|
||||
switch (e.target) {
|
||||
case dropdown_elements().first()[0]:
|
||||
trigger_element_focus($search_input);
|
||||
break;
|
||||
case $search_input[0]:
|
||||
trigger_element_focus(dropdown_elements().last());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "Tab": {
|
||||
switch (e.target) {
|
||||
case $search_input[0]:
|
||||
trigger_element_focus(dropdown_elements().first());
|
||||
break;
|
||||
case dropdown_elements().last()[0]:
|
||||
trigger_element_focus($search_input);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (keydown_util.is_enter_event(e)) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (e.target === $search_input[0]) {
|
||||
// Select the first option from the menu on pressing
|
||||
// "Enter" when focus is on the search input.
|
||||
dropdown_elements().first().trigger("click");
|
||||
} else if ($(e.target).parent().hasClass("list_item")) {
|
||||
$(e.target).trigger("click");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
setup() {
|
||||
// populate the dropdown
|
||||
const $dropdown_list_body = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-list-body`,
|
||||
).expectOne();
|
||||
const $search_input = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-search > input[type=text]`,
|
||||
);
|
||||
const $dropdown_toggle = $(`#${CSS.escape(this.container_id)} .dropdown-toggle`);
|
||||
|
||||
this.setup_dropdown_widget(this.data);
|
||||
|
||||
$(`#${CSS.escape(this.container_id)} .dropdown-search`).on("click", (e) => {
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
this.dropdown_toggle_click_handler();
|
||||
|
||||
$dropdown_toggle.on("focus", (e) => {
|
||||
// On opening a Bootstrap Dropdown, the parent element receives focus.
|
||||
// Here, we want our search input to have focus instead.
|
||||
e.preventDefault();
|
||||
// This function gets called twice when focusing the
|
||||
// dropdown, and only in the second call is the input
|
||||
// field visible in the DOM; so the following visibility
|
||||
// check ensures we wait for the second call to focus.
|
||||
if ($dropdown_list_body.is(":visible")) {
|
||||
$search_input.trigger("focus");
|
||||
}
|
||||
});
|
||||
|
||||
this.dropdown_keyboard_events();
|
||||
|
||||
this.render(this.initial_value);
|
||||
this.register_event_handlers();
|
||||
}
|
||||
|
||||
// Returns the updated value
|
||||
value() {
|
||||
let val = $(`#${CSS.escape(this.container_id)} #${CSS.escape(this.value_id)}`).data(
|
||||
"value",
|
||||
);
|
||||
if (val === null) {
|
||||
val = "";
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
// A widget mostly similar to `DropdownListWidget` but
|
||||
// used in cases of multiple dropdown selection.
|
||||
export class MultiSelectDropdownListWidget extends DropdownListWidget {
|
||||
constructor({
|
||||
widget_name,
|
||||
data,
|
||||
default_text,
|
||||
null_value = null,
|
||||
on_update = () => {},
|
||||
on_close,
|
||||
value,
|
||||
limit,
|
||||
}) {
|
||||
super({
|
||||
widget_name,
|
||||
data,
|
||||
default_text,
|
||||
null_value,
|
||||
on_update,
|
||||
value,
|
||||
});
|
||||
|
||||
// Initializing values specific to `MultiSelectDropdownListWidget`.
|
||||
this.limit = limit;
|
||||
this.on_close = on_close;
|
||||
|
||||
// Important thing to note is that this needs to be maintained as
|
||||
// a reference type and not to deep clone it/assign it to a
|
||||
// different variable, so that it can be later referenced within
|
||||
// `list_widget` as well. The way we manage dropdown elements are
|
||||
// essentially by just modifying the values in `data_selected` variable.
|
||||
this.data_selected = []; // Populate the dropdown values selected by user.
|
||||
|
||||
if (limit === undefined) {
|
||||
this.limit = 2;
|
||||
blueslip.warn(
|
||||
"Multiselect dropdown-list-widget: Called without limit value; using 2 as the limit",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
setup() {
|
||||
super.setup(this);
|
||||
this.initialize_dropdown_values();
|
||||
}
|
||||
|
||||
initialize_dropdown_values() {
|
||||
// Stop the execution if value parameter is undefined and null_value is passed.
|
||||
if (!this.initial_value || this.initial_value === this.null_value) {
|
||||
return;
|
||||
}
|
||||
const $elem = $(`#${CSS.escape(this.container_id)} #${CSS.escape(this.widget_name)}_name`);
|
||||
|
||||
// Push values from initial valued array to `data_selected`.
|
||||
this.data_selected.push(...this.initial_value);
|
||||
this.render_button_text($elem, this.limit);
|
||||
}
|
||||
|
||||
// Set the button text as per the selected data.
|
||||
render_button_text($elem, limit) {
|
||||
const items_selected = this.data_selected.length;
|
||||
let text = "";
|
||||
|
||||
// Destroy the tooltip once the button text reloads.
|
||||
this.destroy_tooltip();
|
||||
|
||||
if (items_selected === 0) {
|
||||
this.render_default_text($elem);
|
||||
return;
|
||||
} else if (limit >= items_selected) {
|
||||
const data_selected = this.data.filter((data) =>
|
||||
this.data_selected.includes(data.value),
|
||||
);
|
||||
text = data_selected.map((data) => data.name).toString();
|
||||
} else {
|
||||
text = $t({defaultMessage: "{items_selected} selected"}, {items_selected});
|
||||
this.render_tooltip();
|
||||
}
|
||||
|
||||
$elem.text(text);
|
||||
$elem.removeClass("text-warning");
|
||||
$elem.closest(".input-group").find(".dropdown_list_reset_button").show();
|
||||
}
|
||||
|
||||
// Override the DrodownListWidget `render` function.
|
||||
render(value) {
|
||||
const $elem = $(`#${CSS.escape(this.container_id)} #${CSS.escape(this.widget_name)}_name`);
|
||||
|
||||
if (!value || value === this.null_value) {
|
||||
this.render_default_text($elem);
|
||||
return;
|
||||
}
|
||||
this.render_button_text($elem, this.limit);
|
||||
}
|
||||
|
||||
dropdown_toggle_click_handler() {
|
||||
const $dropdown_toggle = $(`#${CSS.escape(this.container_id)} .dropdown-toggle`);
|
||||
const $search_input = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-search > input[type=text]`,
|
||||
);
|
||||
|
||||
$dropdown_toggle.on("click", () => {
|
||||
this.reset_dropdown_items();
|
||||
$search_input.val("").trigger("input");
|
||||
});
|
||||
}
|
||||
|
||||
// Cases where a user presses any dropdown item but accidentally closes
|
||||
// the dropdown list.
|
||||
reset_dropdown_items() {
|
||||
// Clear the data selected and stop the execution once the user has
|
||||
// pressed the `reset` button.
|
||||
if (this.is_reset) {
|
||||
this.data_selected.splice(0, this.data_selected.length);
|
||||
return;
|
||||
}
|
||||
|
||||
const original_items = this.checked_items ?? this.initial_value;
|
||||
const items_added = _.difference(this.data_selected, original_items);
|
||||
|
||||
// Removing the unnecessary items from dropdown.
|
||||
for (const val of items_added) {
|
||||
const index = this.data_selected.indexOf(val);
|
||||
if (index > -1) {
|
||||
this.data_selected.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Items that are removed in dropdown but should have been a part of it
|
||||
const items_removed = _.difference(original_items, this.data_selected);
|
||||
this.data_selected.push(...items_removed);
|
||||
}
|
||||
|
||||
// Override the DrodownListWidget `setup_dropdown_widget` function.
|
||||
setup_dropdown_widget(data) {
|
||||
const $dropdown_list_body = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-list-body`,
|
||||
).expectOne();
|
||||
const $search_input = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-search > input[type=text]`,
|
||||
);
|
||||
|
||||
this.list_widget = ListWidget.create($dropdown_list_body, data, {
|
||||
name: `${CSS.escape(this.widget_name)}_list`,
|
||||
get_item: ListWidget.default_get_item,
|
||||
modifier(item) {
|
||||
return render_dropdown_list({item});
|
||||
},
|
||||
multiselect: {
|
||||
selected_items: this.data_selected,
|
||||
},
|
||||
filter: {
|
||||
$element: $search_input,
|
||||
predicate(item, value) {
|
||||
return item.name.toLowerCase().includes(value);
|
||||
},
|
||||
},
|
||||
$simplebar_container: $(`#${CSS.escape(this.container_id)} .dropdown-list-wrapper`),
|
||||
});
|
||||
}
|
||||
|
||||
// Add the check mark to dropdown element passed.
|
||||
add_check_mark($element) {
|
||||
const value = $element.attr("data-value");
|
||||
const $link_elem = $element.find("a").expectOne();
|
||||
$link_elem.prepend($("<i>").addClass(["fa", "fa-check"]));
|
||||
$element.addClass("checked");
|
||||
this.data_selected.push(value);
|
||||
}
|
||||
|
||||
// Remove the check mark from dropdown element.
|
||||
remove_check_mark($element) {
|
||||
const $icon = $element.find("i").expectOne();
|
||||
const value = $element.attr("data-value");
|
||||
const index = this.data_selected.indexOf(value);
|
||||
|
||||
if (index > -1) {
|
||||
$icon.remove();
|
||||
$element.removeClass("checked");
|
||||
this.data_selected.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Render the tooltip once the text changes to `n` selected.
|
||||
render_tooltip() {
|
||||
const $elem = $(`#${CSS.escape(this.container_id)}`);
|
||||
const selected_items = this.data.filter((item) => this.checked_items.includes(item.value));
|
||||
|
||||
tippy($elem[0], {
|
||||
content: selected_items.map((item) => item.name).join(", "),
|
||||
placement: "top",
|
||||
});
|
||||
}
|
||||
|
||||
destroy_tooltip() {
|
||||
const $elem = $(`#${CSS.escape(this.container_id)}`);
|
||||
const tippy_instance = $elem[0]._tippy;
|
||||
if (!tippy_instance) {
|
||||
return;
|
||||
}
|
||||
|
||||
tippy_instance.destroy();
|
||||
}
|
||||
|
||||
dropdown_keyboard_events() {
|
||||
// Main keydown event handler which transfers the focus from one child element
|
||||
// to another.
|
||||
|
||||
const $search_input = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-search > input[type=text]`,
|
||||
);
|
||||
const $dropdown_menu = $(`.${CSS.escape(this.widget_name)}_setting .dropdown-menu`);
|
||||
const $filter_button = $(`#${CSS.escape(this.container_id)} .multiselect_btn`);
|
||||
|
||||
const dropdown_elements = () => {
|
||||
const $dropdown_list_body = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-list-body`,
|
||||
).expectOne();
|
||||
|
||||
return $dropdown_list_body.children().find("a");
|
||||
};
|
||||
|
||||
$dropdown_menu.on("keydown", (e) => {
|
||||
function trigger_element_focus($element) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
$element.trigger("focus");
|
||||
}
|
||||
|
||||
switch (e.key) {
|
||||
case "ArrowDown": {
|
||||
switch (e.target) {
|
||||
case dropdown_elements().last()[0]:
|
||||
trigger_element_focus($filter_button);
|
||||
break;
|
||||
case $(`#${CSS.escape(this.container_id)} .multiselect_btn`)[0]:
|
||||
trigger_element_focus($search_input);
|
||||
break;
|
||||
case $search_input[0]:
|
||||
trigger_element_focus(dropdown_elements().first());
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "ArrowUp": {
|
||||
switch (e.target) {
|
||||
case dropdown_elements().first()[0]:
|
||||
trigger_element_focus($search_input);
|
||||
break;
|
||||
case $search_input[0]:
|
||||
trigger_element_focus($filter_button);
|
||||
break;
|
||||
case $(`#${CSS.escape(this.container_id)} .multiselect_btn`)[0]:
|
||||
trigger_element_focus(dropdown_elements().last());
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "Tab": {
|
||||
switch (e.target) {
|
||||
case $search_input[0]:
|
||||
trigger_element_focus(dropdown_elements().first());
|
||||
break;
|
||||
case $filter_button[0]:
|
||||
trigger_element_focus($search_input);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (keydown_util.is_enter_event(e)) {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (e.target === $search_input[0]) {
|
||||
// Select the first option from the menu on pressing
|
||||
// "Enter" when focus is on the search input.
|
||||
dropdown_elements().first().trigger("click");
|
||||
} else if ($(e.target).parent().hasClass("list_item")) {
|
||||
$(e.target).trigger("click");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Override the `register_event_handlers` function.
|
||||
register_event_handlers() {
|
||||
const $dropdown_list_body = $(
|
||||
`#${CSS.escape(this.container_id)} .dropdown-list-body`,
|
||||
).expectOne();
|
||||
|
||||
$dropdown_list_body.on("click", ".list_item", (e) => {
|
||||
const $element = e.target.closest("li");
|
||||
if ($element.hasClass("checked")) {
|
||||
this.remove_check_mark($element);
|
||||
} else {
|
||||
this.add_check_mark($element);
|
||||
}
|
||||
|
||||
e.stopPropagation();
|
||||
});
|
||||
|
||||
$(`#${CSS.escape(this.container_id)} .dropdown_list_reset_button`).on("click", (e) => {
|
||||
// Default back the values.
|
||||
this.is_reset = true;
|
||||
this.checked_items = undefined;
|
||||
|
||||
this.update(this.null_value);
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
$(`#${CSS.escape(this.container_id)} .multiselect_btn`).on("click", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Set the value to `false` to end the scope of the
|
||||
// `reset` button.
|
||||
this.is_reset = false;
|
||||
// We deep clone the values of `data_selected` to a new
|
||||
// variable. This is so because arrays are reference types
|
||||
// and modifying the parent array can change the values
|
||||
// within the child array. Here, `checked_items` copies over the
|
||||
// value and not just the reference.
|
||||
this.checked_items = _.cloneDeep(this.data_selected);
|
||||
this.update(this.data_selected);
|
||||
|
||||
// Cases when the user wants to pass a successful event after
|
||||
// the dropdown is closed.
|
||||
if (this.on_close) {
|
||||
e.stopPropagation();
|
||||
const $setting_elem = $(e.currentTarget).closest(
|
||||
`.${CSS.escape(this.widget_name)}_setting`,
|
||||
);
|
||||
$setting_elem.find(".dropdown-menu").dropdown("toggle");
|
||||
|
||||
this.on_close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Returns array of values selected by user.
|
||||
value() {
|
||||
let val = this.checked_items;
|
||||
// Cases taken care of -
|
||||
// - User never pressed the filter button -> We return the initial value.
|
||||
// - User pressed the `reset` button -> We return an empty array.
|
||||
if (val === undefined) {
|
||||
val = this.is_reset ? [] : this.initial_value;
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@ import * as channel from "./channel";
|
|||
import * as components from "./components";
|
||||
import * as confirm_dialog from "./confirm_dialog";
|
||||
import * as dialog_widget from "./dialog_widget";
|
||||
import {DropdownListWidget} from "./dropdown_list_widget";
|
||||
import * as dropdown_widget from "./dropdown_widget";
|
||||
import * as hash_util from "./hash_util";
|
||||
import {$t, $t_html} from "./i18n";
|
||||
import * as keydown_util from "./keydown_util";
|
||||
|
@ -193,6 +193,37 @@ export function stream_settings(sub) {
|
|||
return settings;
|
||||
}
|
||||
|
||||
function setup_dropdown(sub, slim_sub) {
|
||||
can_remove_subscribers_group_widget = new dropdown_widget.DropdownWidget({
|
||||
widget_name: "can_remove_subscribers_group",
|
||||
get_options: () =>
|
||||
user_groups.get_realm_user_groups_for_dropdown_list_widget(
|
||||
"can_remove_subscribers_group",
|
||||
),
|
||||
item_click_callback(event, dropdown) {
|
||||
dropdown.hide();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
can_remove_subscribers_group_widget.render();
|
||||
settings_org.save_discard_widget_status_handler(
|
||||
$("#stream_permission_settings"),
|
||||
false,
|
||||
slim_sub,
|
||||
);
|
||||
},
|
||||
$events_container: $("#subscription_overlay .subscription_settings"),
|
||||
tippy_props: {
|
||||
placement: "bottom-start",
|
||||
},
|
||||
default_id: sub.can_remove_subscribers_group,
|
||||
unique_id_type: dropdown_widget.DATA_TYPES.NUMBER,
|
||||
on_mount_callback(dropdown) {
|
||||
$(dropdown.popper).css("min-width", "300px");
|
||||
},
|
||||
});
|
||||
can_remove_subscribers_group_widget.setup();
|
||||
}
|
||||
|
||||
export function show_settings_for(node) {
|
||||
const stream_id = get_stream_id(node);
|
||||
const slim_sub = sub_store.get(stream_id);
|
||||
|
@ -209,24 +240,6 @@ export function show_settings_for(node) {
|
|||
return false;
|
||||
});
|
||||
|
||||
const opts = {
|
||||
widget_name: "can_remove_subscribers_group",
|
||||
data: user_groups.get_realm_user_groups_for_dropdown_list_widget(
|
||||
"can_remove_subscribers_group",
|
||||
),
|
||||
default_text: $t({defaultMessage: "No user groups"}),
|
||||
include_current_item: false,
|
||||
value: sub.can_remove_subscribers_group,
|
||||
on_update() {
|
||||
settings_org.save_discard_widget_status_handler(
|
||||
$("#stream_permission_settings"),
|
||||
false,
|
||||
slim_sub,
|
||||
);
|
||||
},
|
||||
};
|
||||
can_remove_subscribers_group_widget = new DropdownListWidget(opts);
|
||||
|
||||
const html = render_stream_settings({
|
||||
sub,
|
||||
notification_settings,
|
||||
|
@ -257,7 +270,7 @@ export function show_settings_for(node) {
|
|||
show_subscription_settings(sub);
|
||||
settings_org.set_message_retention_setting_dropdown(sub);
|
||||
stream_ui_updates.enable_or_disable_permission_settings_in_edit_panel(sub);
|
||||
can_remove_subscribers_group_widget.setup();
|
||||
setup_dropdown(sub, slim_sub);
|
||||
}
|
||||
|
||||
export function setup_stream_settings(node) {
|
||||
|
|
|
@ -15,7 +15,7 @@ import * as channel from "./channel";
|
|||
import * as components from "./components";
|
||||
import * as compose_state from "./compose_state";
|
||||
import * as confirm_dialog from "./confirm_dialog";
|
||||
import {DropdownListWidget} from "./dropdown_list_widget";
|
||||
import * as dropdown_widget from "./dropdown_widget";
|
||||
import * as hash_util from "./hash_util";
|
||||
import {$t, $t_html} from "./i18n";
|
||||
import * as keydown_util from "./keydown_util";
|
||||
|
@ -589,6 +589,32 @@ export function switch_stream_sort(tab_name) {
|
|||
|
||||
export let new_stream_can_remove_subscribers_group_widget = null;
|
||||
|
||||
function dropdown_setup() {
|
||||
new_stream_can_remove_subscribers_group_widget = new dropdown_widget.DropdownWidget({
|
||||
widget_name: "new_stream_can_remove_subscribers_group",
|
||||
get_options: () =>
|
||||
user_groups.get_realm_user_groups_for_dropdown_list_widget(
|
||||
"can_remove_subscribers_group",
|
||||
),
|
||||
item_click_callback(event, dropdown) {
|
||||
dropdown.hide();
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
new_stream_can_remove_subscribers_group_widget.render();
|
||||
},
|
||||
$events_container: $("#subscription_overlay"),
|
||||
tippy_props: {
|
||||
placement: "bottom-start",
|
||||
},
|
||||
on_mount_callback(dropdown) {
|
||||
$(dropdown.popper).css("min-width", "300px");
|
||||
},
|
||||
default_text: $t({defaultMessage: "No user groups"}),
|
||||
default_id: user_groups.get_user_group_from_name("role:administrators").id,
|
||||
unique_id_type: dropdown_widget.DATA_TYPES.NUMBER,
|
||||
});
|
||||
}
|
||||
|
||||
export function setup_page(callback) {
|
||||
// We should strongly consider only setting up the page once,
|
||||
// but I am writing these comments write before a big release,
|
||||
|
@ -664,17 +690,6 @@ export function setup_page(callback) {
|
|||
function populate_and_fill() {
|
||||
$("#streams_overlay_container").empty();
|
||||
|
||||
const opts = {
|
||||
widget_name: "new_stream_can_remove_subscribers_group",
|
||||
data: user_groups.get_realm_user_groups_for_dropdown_list_widget(
|
||||
"can_remove_subscribers_group",
|
||||
),
|
||||
default_text: $t({defaultMessage: "No user groups"}),
|
||||
include_current_item: false,
|
||||
value: user_groups.get_user_group_from_name("role:administrators").id,
|
||||
};
|
||||
new_stream_can_remove_subscribers_group_widget = new DropdownListWidget(opts);
|
||||
|
||||
// TODO: Ideally we'd indicate in some way what stream types
|
||||
// the user can create, by showing other options as disabled.
|
||||
const stream_privacy_policy = stream_data.stream_privacy_policy_values.public.code;
|
||||
|
@ -711,6 +726,7 @@ export function setup_page(callback) {
|
|||
|
||||
render_left_panel_superset();
|
||||
initialize_components();
|
||||
dropdown_setup();
|
||||
redraw_left_panel();
|
||||
stream_create.set_up_handlers();
|
||||
|
||||
|
|
|
@ -21,7 +21,7 @@ type UserGroupRaw = Omit<UserGroup, "members"> & {members: number[]};
|
|||
|
||||
type UserGroupForDropdownListWidget = {
|
||||
name: string;
|
||||
value: string;
|
||||
unique_id: number;
|
||||
};
|
||||
|
||||
let user_group_name_dict: FoldDict<UserGroup>;
|
||||
|
@ -257,7 +257,7 @@ export function get_realm_user_groups_for_dropdown_list_widget(
|
|||
}
|
||||
return {
|
||||
name: group.display_name,
|
||||
value: user_group.id.toString(),
|
||||
unique_id: user_group.id,
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -267,7 +267,7 @@ export function get_realm_user_groups_for_dropdown_list_widget(
|
|||
|
||||
const user_groups_excluding_system_groups = get_realm_user_groups().map((group) => ({
|
||||
name: group.name,
|
||||
value: group.id.toString(),
|
||||
unique_id: group.id,
|
||||
}));
|
||||
|
||||
return [...system_user_groups, ...user_groups_excluding_system_groups];
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
<div class="input-group dropdown-list-widget" id="{{widget_name}}_widget">
|
||||
<span class="{{widget_name}}_setting dropup actual-dropdown-menu prop-element" id="id_{{widget_name}}" aria-labelledby="{{widget_name}}_label" data-setting-widget-type="dropdown-list-widget" {{#if value_type}}data-setting-value-type="{{value_type}}"{{/if}}>
|
||||
{{#if label}}
|
||||
<label id="{{widget_name}}_label" class="dropdown-title">
|
||||
{{ label }}
|
||||
</label>
|
||||
{{/if}}
|
||||
<button class="button rounded dropdown-toggle" type="button" data-toggle="dropdown">
|
||||
<span id="{{widget_name}}_name"></span>
|
||||
<i class="fa fa-chevron-down"></i>
|
||||
</button>
|
||||
<ul class="dropdown-menu modal-bg" role="menu">
|
||||
<li class="dropdown-search" role="presentation">
|
||||
<input class="no-input-change-detection filter_text_input" type="text" role="menuitem" placeholder="{{list_placeholder}}" autofocus/>
|
||||
</li>
|
||||
<div class="dropdown-list-wrapper" data-simplebar>
|
||||
<span class="dropdown-list-body"></span>
|
||||
{{#if filter_button_text }}
|
||||
<button class="button rounded small sea-green multiselect_btn" tabindex="0">
|
||||
<i class="fa fa-filter" aria-hidden="true"></i>
|
||||
{{filter_button_text}}
|
||||
</button>
|
||||
{{/if}}
|
||||
</div>
|
||||
</ul>
|
||||
</span>
|
||||
{{#if reset_button_text}}
|
||||
<a class="dropdown_list_reset_button" role="button">
|
||||
{{reset_button_text}}
|
||||
</a>
|
||||
{{/if}}
|
||||
</div>
|
|
@ -29,14 +29,10 @@
|
|||
</select>
|
||||
</div>
|
||||
|
||||
<div class="input-group new-style">
|
||||
<label class="dropdown-title">{{t 'Who can unsubscribe others from this stream?'}}
|
||||
</label>
|
||||
{{> ../settings/dropdown_list_widget
|
||||
widget_name=can_remove_subscribers_setting_widget_name
|
||||
list_placeholder=(t 'Filter roles')
|
||||
value_type="number" }}
|
||||
</div>
|
||||
{{> ../dropdown_widget_with_label
|
||||
widget_name=can_remove_subscribers_setting_widget_name
|
||||
label=(t 'Who can unsubscribe others from this stream?')
|
||||
value_type="number"}}
|
||||
|
||||
{{#if (or is_owner is_stream_edit)}}
|
||||
<div>
|
||||
|
|
|
@ -1,215 +0,0 @@
|
|||
"use strict";
|
||||
|
||||
const {strict: assert} = require("assert");
|
||||
|
||||
const {$t} = require("./lib/i18n");
|
||||
const {mock_esm, zrequire, set_global} = require("./lib/namespace");
|
||||
const {run_test} = require("./lib/test");
|
||||
const blueslip = require("./lib/zblueslip");
|
||||
const $ = require("./lib/zjquery");
|
||||
|
||||
const noop = () => {};
|
||||
mock_esm("../src/list_widget", {
|
||||
create: () => ({init: noop}),
|
||||
});
|
||||
|
||||
mock_esm("tippy.js", {
|
||||
default(arg) {
|
||||
arg._tippy = {setContent: noop, placement: noop, destroy: noop};
|
||||
return arg._tippy;
|
||||
},
|
||||
});
|
||||
|
||||
set_global("document", {});
|
||||
const {DropdownListWidget, MultiSelectDropdownListWidget} = zrequire("dropdown_list_widget");
|
||||
|
||||
// For DropdownListWidget
|
||||
const setup_dropdown_zjquery_data = (name) => {
|
||||
const $input_group = $(".input_group");
|
||||
const $reset_button = $(".dropdown_list_reset_button");
|
||||
$input_group.set_find_results(".dropdown_list_reset_button", $reset_button);
|
||||
$(`#${CSS.escape(name)}_widget #${CSS.escape(name)}_name`).closest = () => $input_group;
|
||||
const $widget = $(`#${CSS.escape(name)}_widget #${CSS.escape(name)}_name`);
|
||||
return {$reset_button, $widget};
|
||||
};
|
||||
|
||||
run_test("basic_functions", () => {
|
||||
let updated_value;
|
||||
const opts = {
|
||||
widget_name: "my_setting",
|
||||
data: ["one", "two", "three"].map((x) => ({name: x, value: x})),
|
||||
value: "one",
|
||||
on_update(val) {
|
||||
updated_value = val;
|
||||
},
|
||||
default_text: $t({defaultMessage: "not set"}),
|
||||
render_text: (text) => `rendered: ${text}`,
|
||||
};
|
||||
|
||||
const {$reset_button, $widget} = setup_dropdown_zjquery_data(opts.widget_name);
|
||||
|
||||
const widget = new DropdownListWidget(opts);
|
||||
widget.setup();
|
||||
|
||||
assert.equal(widget.value(), "one");
|
||||
assert.equal(updated_value, undefined); // We haven't 'updated' the widget yet.
|
||||
assert.ok($reset_button.visible());
|
||||
|
||||
widget.update("two");
|
||||
assert.equal($widget.text(), "rendered: two");
|
||||
assert.equal(widget.value(), "two");
|
||||
assert.equal(updated_value, "two");
|
||||
assert.ok($reset_button.visible());
|
||||
|
||||
widget.update(null);
|
||||
assert.equal($widget.text(), "translated: not set");
|
||||
assert.equal(widget.value(), "");
|
||||
assert.equal(updated_value, null);
|
||||
assert.ok(!$reset_button.visible());
|
||||
|
||||
widget.update("four");
|
||||
assert.equal($widget.text(), "translated: not set");
|
||||
assert.equal(widget.value(), "four");
|
||||
assert.equal(updated_value, "four");
|
||||
assert.ok(!$reset_button.visible());
|
||||
});
|
||||
|
||||
run_test("no_default_value", () => {
|
||||
const opts = {
|
||||
widget_name: "my_setting",
|
||||
data: ["one", "two", "three"].map((x) => ({name: x, value: x})),
|
||||
default_text: $t({defaultMessage: "not set"}),
|
||||
render_text: /* istanbul ignore next */ (text) => `rendered: ${text}`,
|
||||
null_value: "null-value",
|
||||
};
|
||||
|
||||
blueslip.expect(
|
||||
"warn",
|
||||
"dropdown-list-widget: Called without a default value; using null value",
|
||||
);
|
||||
setup_dropdown_zjquery_data(opts.widget_name);
|
||||
const widget = new DropdownListWidget(opts);
|
||||
widget.setup();
|
||||
assert.equal(widget.value(), "null-value");
|
||||
});
|
||||
|
||||
// For MultiSelectDropdownListWidget
|
||||
const setup_multiselect_dropdown_zjquery_data = function (name) {
|
||||
$(`#${CSS.escape(name)}_widget`)[0] = {};
|
||||
return setup_dropdown_zjquery_data(name);
|
||||
};
|
||||
|
||||
run_test("basic MDLW functions", () => {
|
||||
let updated_value;
|
||||
const opts = {
|
||||
widget_name: "my_setting",
|
||||
data: ["one", "two", "three", "four"].map((x) => ({name: x, value: x})),
|
||||
value: ["one"],
|
||||
limit: 2,
|
||||
on_update(val) {
|
||||
updated_value = val;
|
||||
},
|
||||
default_text: $t({defaultMessage: "not set"}),
|
||||
};
|
||||
|
||||
const {$reset_button, $widget} = setup_multiselect_dropdown_zjquery_data(opts.widget_name);
|
||||
const widget = new MultiSelectDropdownListWidget(opts);
|
||||
widget.setup();
|
||||
|
||||
function set_dropdown_variables(widget, value) {
|
||||
widget.data_selected = value;
|
||||
widget.checked_items = value;
|
||||
}
|
||||
|
||||
assert.deepEqual(widget.value(), ["one"]);
|
||||
assert.equal(updated_value, undefined);
|
||||
assert.equal($widget.text(), "one");
|
||||
assert.ok($reset_button.visible());
|
||||
|
||||
set_dropdown_variables(widget, ["one", "two"]);
|
||||
widget.update(widget.data_selected);
|
||||
|
||||
assert.equal($widget.text(), "one,two");
|
||||
assert.deepEqual(widget.value(), ["one", "two"]);
|
||||
assert.deepEqual(updated_value, ["one", "two"]);
|
||||
assert.ok($reset_button.visible());
|
||||
|
||||
set_dropdown_variables(widget, ["one", "two", "three"]);
|
||||
widget.update(widget.data_selected);
|
||||
|
||||
assert.equal($widget.text(), "translated: 3 selected");
|
||||
assert.deepEqual(widget.value(), ["one", "two", "three"]);
|
||||
assert.deepEqual(updated_value, ["one", "two", "three"]);
|
||||
assert.ok($reset_button.visible());
|
||||
|
||||
set_dropdown_variables(widget, null);
|
||||
widget.update(widget.data_selected);
|
||||
|
||||
assert.equal($widget.text(), "translated: not set");
|
||||
assert.equal(widget.value(), null);
|
||||
assert.equal(updated_value, null);
|
||||
assert.ok(!$reset_button.visible());
|
||||
|
||||
set_dropdown_variables(widget, ["one"]);
|
||||
widget.update(widget.data_selected);
|
||||
|
||||
assert.equal($widget.text(), "one");
|
||||
assert.deepEqual(widget.value(), ["one"]);
|
||||
assert.deepEqual(updated_value, ["one"]);
|
||||
assert.ok($reset_button.visible());
|
||||
});
|
||||
|
||||
run_test("MDLW no_default_value", () => {
|
||||
const opts = {
|
||||
widget_name: "my_setting",
|
||||
data: ["one", "two", "three", "four"].map((x) => ({name: x, value: x})),
|
||||
limit: 2,
|
||||
null_value: "null-value",
|
||||
default_text: $t({defaultMessage: "not set"}),
|
||||
};
|
||||
|
||||
blueslip.expect(
|
||||
"warn",
|
||||
"dropdown-list-widget: Called without a default value; using null value",
|
||||
);
|
||||
|
||||
setup_multiselect_dropdown_zjquery_data(opts.widget_name);
|
||||
const widget = new MultiSelectDropdownListWidget(opts);
|
||||
widget.setup();
|
||||
|
||||
assert.equal(widget.value(), "null-value");
|
||||
});
|
||||
|
||||
run_test("MDLW no_limit_set", () => {
|
||||
const opts = {
|
||||
widget_name: "my_setting",
|
||||
data: ["one", "two", "three", "four"].map((x) => ({name: x, value: x})),
|
||||
value: ["one"],
|
||||
default_text: $t({defaultMessage: "not set"}),
|
||||
};
|
||||
|
||||
blueslip.expect(
|
||||
"warn",
|
||||
"Multiselect dropdown-list-widget: Called without limit value; using 2 as the limit",
|
||||
);
|
||||
|
||||
function set_dropdown_variables(widget, value) {
|
||||
widget.data_selected = value;
|
||||
widget.checked_items = value;
|
||||
}
|
||||
|
||||
const {$widget} = setup_multiselect_dropdown_zjquery_data(opts.widget_name);
|
||||
const widget = new MultiSelectDropdownListWidget(opts);
|
||||
widget.setup();
|
||||
|
||||
set_dropdown_variables(widget, ["one", "two", "three"]);
|
||||
widget.update(widget.data_selected);
|
||||
|
||||
// limit is set to 2 (Default value).
|
||||
assert.equal($widget.text(), "translated: 3 selected");
|
||||
|
||||
set_dropdown_variables(widget, ["one"]);
|
||||
widget.update(widget.data_selected);
|
||||
|
||||
assert.equal($widget.text(), "one");
|
||||
});
|
|
@ -318,11 +318,11 @@ run_test("get_realm_user_groups_for_dropdown_list_widget", () => {
|
|||
};
|
||||
|
||||
const expected_groups_list = [
|
||||
{name: "translated: Admins, moderators, members and guests", value: "6"},
|
||||
{name: "translated: Admins, moderators and members", value: "5"},
|
||||
{name: "translated: Admins, moderators and full members", value: "7"},
|
||||
{name: "translated: Admins and moderators", value: "4"},
|
||||
{name: "translated: Admins", value: "3"},
|
||||
{name: "translated: Admins, moderators, members and guests", unique_id: 6},
|
||||
{name: "translated: Admins, moderators and members", unique_id: 5},
|
||||
{name: "translated: Admins, moderators and full members", unique_id: 7},
|
||||
{name: "translated: Admins and moderators", unique_id: 4},
|
||||
{name: "translated: Admins", unique_id: 3},
|
||||
];
|
||||
|
||||
user_groups.initialize({
|
||||
|
|
Loading…
Reference in New Issue