mirror of
https://github.com/zulip/zulip.git
synced 2025-11-10 17:07:07 +00:00
This is a fairly straightforward extraction. It's good to test this with Iago, and then go into Manage Streams and add/remove subscribers for a stream like devel. I copy/pasted two small functions that will soon diverge from stream_edit. The get_stream_id function will either use a module variable (since we're generally only editing subscribers for one stream, and we already have the singleton assumption with `input_pill`) or a more strict CSS selector. And then get_sub_for_target depends on get_stream_id. We may not always need full subs, anyway, and when we adapt some of this code for creating streams, things are likely to change. I stopped exporting a couple functions that have no callers outside of this module. The main entry point for the module is enable_subscriber_management. We continue to export invite_user_to_stream and remove_user_from_stream, which should possibly be just pulled into their own module to lessen some dependencies, but they don't have too much baggage, since they just wrap channel calls.
337 lines
11 KiB
JavaScript
337 lines
11 KiB
JavaScript
import $ from "jquery";
|
|
|
|
import render_unsubscribe_private_stream_modal from "../templates/confirm_dialog/confirm_unsubscribe_private_stream.hbs";
|
|
import render_stream_member_list_entry from "../templates/stream_settings/stream_member_list_entry.hbs";
|
|
import render_stream_subscription_request_result from "../templates/stream_settings/stream_subscription_request_result.hbs";
|
|
|
|
import * as blueslip from "./blueslip";
|
|
import * as channel from "./channel";
|
|
import * as confirm_dialog from "./confirm_dialog";
|
|
import {$t, $t_html} from "./i18n";
|
|
import * as input_pill from "./input_pill";
|
|
import * as ListWidget from "./list_widget";
|
|
import {page_params} from "./page_params";
|
|
import * as peer_data from "./peer_data";
|
|
import * as people from "./people";
|
|
import * as pill_typeahead from "./pill_typeahead";
|
|
import * as settings_data from "./settings_data";
|
|
import * as stream_pill from "./stream_pill";
|
|
import * as sub_store from "./sub_store";
|
|
import * as ui from "./ui";
|
|
import * as user_group_pill from "./user_group_pill";
|
|
import * as user_pill from "./user_pill";
|
|
|
|
export let pill_widget;
|
|
|
|
function create_item_from_text(text, current_items) {
|
|
const funcs = [
|
|
stream_pill.create_item_from_stream_name,
|
|
user_group_pill.create_item_from_group_name,
|
|
user_pill.create_item_from_email,
|
|
];
|
|
for (const func of funcs) {
|
|
const item = func(text, current_items);
|
|
if (item) {
|
|
return item;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function get_text_from_item(item) {
|
|
const funcs = [
|
|
stream_pill.get_stream_name_from_item,
|
|
user_group_pill.get_group_name_from_item,
|
|
user_pill.get_email_from_item,
|
|
];
|
|
for (const func of funcs) {
|
|
const text = func(item);
|
|
if (text) {
|
|
return text;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function format_member_list_elem(person) {
|
|
return render_stream_member_list_entry({
|
|
name: person.full_name,
|
|
user_id: person.user_id,
|
|
email: settings_data.email_for_user_settings(person),
|
|
displaying_for_admin: page_params.is_admin,
|
|
show_email: settings_data.show_email(),
|
|
});
|
|
}
|
|
|
|
function get_stream_id(target) {
|
|
// TODO: Use more precise selector.
|
|
const row = $(target).closest(
|
|
".stream-row, .stream_settings_header, .subscription_settings, .save-button",
|
|
);
|
|
return Number.parseInt(row.attr("data-stream-id"), 10);
|
|
}
|
|
|
|
function get_sub_for_target(target) {
|
|
const stream_id = get_stream_id(target);
|
|
if (!stream_id) {
|
|
blueslip.error("Cannot find stream id for target");
|
|
return undefined;
|
|
}
|
|
|
|
const sub = sub_store.get(stream_id);
|
|
if (!sub) {
|
|
blueslip.error("get_sub_for_target() failed id lookup: " + stream_id);
|
|
return undefined;
|
|
}
|
|
return sub;
|
|
}
|
|
|
|
function show_stream_subscription_request_result({
|
|
message,
|
|
add_class,
|
|
remove_class,
|
|
subscribed_users,
|
|
already_subscribed_users,
|
|
ignored_deactivated_users,
|
|
}) {
|
|
const stream_subscription_req_result_elem = $(
|
|
".stream_subscription_request_result",
|
|
).expectOne();
|
|
const html = render_stream_subscription_request_result({
|
|
message,
|
|
subscribed_users,
|
|
already_subscribed_users,
|
|
ignored_deactivated_users,
|
|
});
|
|
ui.get_content_element(stream_subscription_req_result_elem).html(html);
|
|
if (add_class) {
|
|
stream_subscription_req_result_elem.addClass(add_class);
|
|
}
|
|
if (remove_class) {
|
|
stream_subscription_req_result_elem.removeClass(remove_class);
|
|
}
|
|
}
|
|
|
|
export function enable_subscriber_management({sub, parent_container}) {
|
|
const stream_id = sub.stream_id;
|
|
|
|
const pill_container = parent_container.find(".pill-container");
|
|
|
|
pill_widget = input_pill.create({
|
|
container: pill_container,
|
|
create_item_from_text,
|
|
get_text_from_item,
|
|
});
|
|
|
|
const user_ids = peer_data.get_subscribers(stream_id);
|
|
const users = people.get_users_from_ids(user_ids);
|
|
people.sort_but_pin_current_user_on_top(users);
|
|
|
|
function get_users_for_subscriber_typeahead() {
|
|
const potential_subscribers = peer_data.potential_subscribers(stream_id);
|
|
return user_pill.filter_taken_users(potential_subscribers, pill_widget);
|
|
}
|
|
|
|
const list_container = parent_container.find(".subscriber_table");
|
|
list_container.empty();
|
|
|
|
const simplebar_container = parent_container.find(".subscriber_list_container");
|
|
|
|
ListWidget.create(list_container, users, {
|
|
name: "stream_subscribers/" + stream_id,
|
|
modifier(item) {
|
|
return format_member_list_elem(item);
|
|
},
|
|
filter: {
|
|
element: $(`[data-stream-id='${CSS.escape(stream_id)}'] .search`),
|
|
predicate(person, value) {
|
|
const matcher = people.build_person_matcher(value);
|
|
const match = matcher(person);
|
|
|
|
return match;
|
|
},
|
|
},
|
|
simplebar_container,
|
|
});
|
|
|
|
const opts = {
|
|
user_source: get_users_for_subscriber_typeahead,
|
|
stream: true,
|
|
user_group: true,
|
|
user: true,
|
|
};
|
|
pill_typeahead.set_up(pill_container.find(".input"), pill_widget, opts);
|
|
}
|
|
|
|
export function invite_user_to_stream(user_ids, sub, success, failure) {
|
|
// TODO: use stream_id when backend supports it
|
|
const stream_name = sub.name;
|
|
return channel.post({
|
|
url: "/json/users/me/subscriptions",
|
|
data: {
|
|
subscriptions: JSON.stringify([{name: stream_name}]),
|
|
principals: JSON.stringify(user_ids),
|
|
},
|
|
success,
|
|
error: failure,
|
|
});
|
|
}
|
|
|
|
export function remove_user_from_stream(user_id, sub, success, failure) {
|
|
// TODO: use stream_id when backend supports it
|
|
const stream_name = sub.name;
|
|
return channel.del({
|
|
url: "/json/users/me/subscriptions",
|
|
data: {subscriptions: JSON.stringify([stream_name]), principals: JSON.stringify([user_id])},
|
|
success,
|
|
error: failure,
|
|
});
|
|
}
|
|
|
|
function submit_add_subscriber_form(e) {
|
|
const sub = get_sub_for_target(e.target);
|
|
if (!sub) {
|
|
blueslip.error(".subscriber_list_add form submit fails");
|
|
return;
|
|
}
|
|
|
|
let user_ids = user_pill.get_user_ids(pill_widget);
|
|
user_ids = user_ids.concat(stream_pill.get_user_ids(pill_widget));
|
|
user_ids = user_ids.concat(user_group_pill.get_user_ids(pill_widget));
|
|
const deactivated_users = new Set();
|
|
user_ids = user_ids.filter((user_id) => {
|
|
if (!people.is_person_active(user_id)) {
|
|
deactivated_users.add(user_id);
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
user_ids = new Set(user_ids);
|
|
|
|
if (user_ids.has(page_params.user_id) && sub.subscribed) {
|
|
// We don't want to send a request to subscribe ourselves
|
|
// if we are already subscribed to this stream. This
|
|
// case occurs when creating user pills from a stream.
|
|
user_ids.delete(page_params.user_id);
|
|
}
|
|
let ignored_deactivated_users;
|
|
if (deactivated_users.size > 0) {
|
|
ignored_deactivated_users = Array.from(deactivated_users);
|
|
ignored_deactivated_users = ignored_deactivated_users.map((user_id) =>
|
|
people.get_by_user_id(user_id),
|
|
);
|
|
}
|
|
if (user_ids.size === 0) {
|
|
show_stream_subscription_request_result({
|
|
message: $t({defaultMessage: "No user to subscribe."}),
|
|
add_class: "text-error",
|
|
remove_class: "text-success",
|
|
ignored_deactivated_users,
|
|
});
|
|
return;
|
|
}
|
|
user_ids = Array.from(user_ids);
|
|
|
|
function invite_success(data) {
|
|
pill_widget.clear();
|
|
const subscribed_users = Object.keys(data.subscribed).map((email) =>
|
|
people.get_by_email(email),
|
|
);
|
|
const already_subscribed_users = Object.keys(data.already_subscribed).map((email) =>
|
|
people.get_by_email(email),
|
|
);
|
|
|
|
show_stream_subscription_request_result({
|
|
add_class: "text-success",
|
|
remove_class: "text-error",
|
|
subscribed_users,
|
|
already_subscribed_users,
|
|
ignored_deactivated_users,
|
|
});
|
|
}
|
|
|
|
function invite_failure(xhr) {
|
|
const error = JSON.parse(xhr.responseText);
|
|
show_stream_subscription_request_result({
|
|
message: error.msg,
|
|
add_class: "text-error",
|
|
remove_class: "text-success",
|
|
});
|
|
}
|
|
|
|
invite_user_to_stream(user_ids, sub, invite_success, invite_failure);
|
|
}
|
|
|
|
export function initialize() {
|
|
$("#subscriptions_table").on("keyup", ".subscriber_list_add form", (e) => {
|
|
if (e.key === "Enter") {
|
|
e.preventDefault();
|
|
submit_add_subscriber_form(e);
|
|
}
|
|
});
|
|
|
|
$("#subscriptions_table").on("submit", ".subscriber_list_add form", (e) => {
|
|
e.preventDefault();
|
|
submit_add_subscriber_form(e);
|
|
});
|
|
|
|
$("#subscriptions_table").on("submit", ".subscriber_list_remove form", (e) => {
|
|
e.preventDefault();
|
|
|
|
const list_entry = $(e.target).closest("tr");
|
|
const target_user_id = Number.parseInt(list_entry.attr("data-subscriber-id"), 10);
|
|
|
|
const sub = get_sub_for_target(e.target);
|
|
if (!sub) {
|
|
blueslip.error(".subscriber_list_remove form submit fails");
|
|
return;
|
|
}
|
|
let message;
|
|
|
|
function removal_success(data) {
|
|
if (data.removed.length > 0) {
|
|
// Remove the user from the subscriber list.
|
|
list_entry.remove();
|
|
message = $t({defaultMessage: "Unsubscribed successfully!"});
|
|
// The rest of the work is done via the subscription -> remove event we will get
|
|
} else {
|
|
message = $t({defaultMessage: "User is already not subscribed."});
|
|
}
|
|
show_stream_subscription_request_result({
|
|
message,
|
|
add_class: "text-success",
|
|
remove_class: "text-remove",
|
|
});
|
|
}
|
|
|
|
function removal_failure() {
|
|
show_stream_subscription_request_result({
|
|
message: $t({defaultMessage: "Error removing user from this stream."}),
|
|
add_class: "text-error",
|
|
remove_class: "text-success",
|
|
});
|
|
}
|
|
|
|
function remove_user_from_private_stream() {
|
|
remove_user_from_stream(target_user_id, sub, removal_success, removal_failure);
|
|
}
|
|
|
|
if (sub.invite_only && people.is_my_user_id(target_user_id)) {
|
|
const html_body = render_unsubscribe_private_stream_modal();
|
|
|
|
confirm_dialog.launch({
|
|
html_heading: $t_html(
|
|
{defaultMessage: "Unsubscribe from {stream_name}"},
|
|
{stream_name: sub.name},
|
|
),
|
|
html_body,
|
|
on_click: remove_user_from_private_stream,
|
|
});
|
|
return;
|
|
}
|
|
|
|
remove_user_from_stream(target_user_id, sub, removal_success, removal_failure);
|
|
});
|
|
}
|