Extract stream_edit.js.

This code makes the right pane work in "Manage Streams" when
you are editing a stream subscription.  It handles basic
functionality (submitting forms, etc.), live updates, and
showing the pane as needed.

Most of the code here was simply moved from subs.js, but some
functions were pulled out of larger functions:

    live update:
        add_me_to_member_list
        update_stream_name
        update_stream_description

    collapse/show:
        collapse
        show_sub

We also now export subs.show_subs_pane.

We eventually want stream_edit not to call into subs.js, and
this should be fairly easy--we just need to move some shared
methods to a new module.
This commit is contained in:
Steve Howell 2017-04-23 19:11:25 -07:00 committed by Tim Abbott
parent ca2aea8d01
commit 7326971380
9 changed files with 620 additions and 559 deletions

View File

@ -58,6 +58,7 @@
"compose_fade": false,
"modals": false,
"stream_create": false,
"stream_edit": false,
"subs": false,
"stream_muting": false,
"stream_events": false,

View File

@ -466,8 +466,8 @@ $(function () {
(function () {
var map = {
".stream-description-editable": subs.change_stream_description,
".stream-name-editable": subs.change_stream_name,
".stream-description-editable": stream_edit.change_stream_description,
".stream-name-editable": stream_edit.change_stream_name,
};
// http://stackoverflow.com/questions/4233265/contenteditable-set-caret-at-the-end-of-the-text-cross-browser

View File

@ -734,7 +734,7 @@ $(function () {
return;
}
subs.invite_user_to_stream(email, sub, success, failure);
stream_edit.invite_user_to_stream(email, sub, success, failure);
});
$("#compose_invite_users").on('click', '.compose_invite_close', function (event) {

View File

@ -103,12 +103,12 @@ exports.set_up = function () {
function update_desktop_notification_setting(new_setting) {
update_global_stream_setting("enable_stream_desktop_notifications", new_setting);
subs.set_all_stream_desktop_notifications_to(new_setting);
stream_edit.set_all_stream_desktop_notifications_to(new_setting);
}
function update_audible_notification_setting(new_setting) {
update_global_stream_setting("enable_stream_sounds", new_setting);
subs.set_all_stream_audible_notifications_to(new_setting);
stream_edit.set_all_stream_audible_notifications_to(new_setting);
}
function maybe_bulk_update_stream_notification_setting(notification_checkbox,

584
static/js/stream_edit.js Normal file
View File

@ -0,0 +1,584 @@
var stream_edit = (function () {
var exports = {};
function setup_subscriptions_stream_hash(sub, stream_id) {
subs.change_state.prevent_once();
window.location.hash = "#streams" + "/" +
stream_id + "/" +
hash_util.encodeHashComponent(sub.name);
}
function settings_for_sub(sub) {
var id = parseInt(sub.stream_id, 10);
return $("#subscription_overlay .subscription_settings[data-stream-id='" + id + "']");
}
exports.collapse = function (sub) {
// I am not sure whether this code is really correct; it was extracted
// from subs.update_settings_for_unsubscribed() and possibly pre-dates
// our big streams re-design in late 2016.
var stream_settings = settings_for_sub(sub);
if (stream_settings.hasClass('in')) {
stream_settings.collapse('hide');
}
var sub_row = stream_settings.closest('.stream-row');
sub_row.find(".regular_subscription_settings").removeClass('in');
};
exports.show_sub = function (sub) {
var stream_settings = settings_for_sub(sub);
var sub_row = stream_settings.closest('.stream-row');
sub_row.find(".regular_subscription_settings").addClass('in');
};
exports.add_me_to_member_list = function (sub) {
// Add the user to the member list if they're currently
// viewing the members of this stream
var stream_settings = settings_for_sub(sub);
if (sub.render_subscribers && stream_settings.hasClass('in')) {
exports.prepend_subscriber(
stream_settings,
people.my_current_email());
}
};
exports.show_stream_row = function (node, show_settings) {
$(".display-type #add_new_stream_title").hide();
$(".display-type #stream_settings_title, .right .settings").show();
$(".stream-row.active").removeClass("active");
if (show_settings) {
subs.show_subs_pane.settings();
$(node).addClass("active");
stream_edit.show_settings_for(node);
} else {
subs.show_subs_pane.nothing_selected();
}
};
function format_member_list_elem(email) {
var person = people.get_by_email(email);
return templates.render('stream_member_list_entry',
{name: person.full_name, email: email,
displaying_for_admin: page_params.is_admin});
}
function get_subscriber_list(sub_row) {
var id = sub_row.data("stream-id");
return $('.subscription_settings[data-stream-id="' + id + '"] .subscriber-list');
}
exports.update_stream_name = function (sub, new_name) {
var sub_settings = settings_for_sub(sub);
sub_settings.find(".email-address").text(sub.email_address);
sub_settings.find(".stream-name-editable").text(new_name);
};
exports.update_stream_description = function (sub) {
var stream_settings = settings_for_sub(sub);
stream_settings.find('input.description').val(sub.description);
stream_settings.find('.stream-description-editable').html(sub.rendered_description);
};
exports.prepend_subscriber = function (sub_row, email) {
var list = get_subscriber_list(sub_row);
list.prepend(format_member_list_elem(email));
};
exports.invite_user_to_stream = function (user_email, sub, success, failure) {
// TODO: use stream_id when backend supports it
var stream_name = sub.name;
return channel.post({
url: "/json/users/me/subscriptions",
data: {subscriptions: JSON.stringify([{name: stream_name}]),
principals: JSON.stringify([user_email])},
success: success,
error: failure,
});
};
exports.remove_user_from_stream = function (user_email, sub, success, failure) {
// TODO: use stream_id when backend supports it
var stream_name = sub.name;
return channel.del({
url: "/json/users/me/subscriptions",
data: {subscriptions: JSON.stringify([stream_name]),
principals: JSON.stringify([user_email])},
success: success,
error: failure,
});
};
function get_stream_id(target) {
if (target.constructor !== jQuery) {
target = $(target);
}
return target.closest(".stream-row, .subscription_settings").attr("data-stream-id");
}
function get_sub_for_target(target) {
var stream_id = get_stream_id(target);
if (!stream_id) {
blueslip.error('Cannot find stream id for target');
return;
}
var sub = stream_data.get_sub_by_id(stream_id);
if (!sub) {
blueslip.error('get_sub_for_target() failed id lookup: ' + stream_id);
return;
}
return sub;
}
function show_subscription_settings(sub_row) {
var stream_id = sub_row.data("stream-id");
var sub = stream_data.get_sub_by_id(stream_id);
var sub_settings = settings_for_sub(sub);
var alerts = sub_settings
.find('.subscriber_list_container')
.find('.alert-warning, .alert-error');
var colorpicker = sub_settings.find('.colorpicker');
var color = stream_data.get_color(sub.name);
stream_color.set_colorpicker_color(colorpicker, color);
if (!sub.render_subscribers) {
return;
}
// fetch subscriber list from memory.
var list = get_subscriber_list(sub_settings);
alerts.addClass("hide");
list.empty();
var emails = [];
sub.subscribers.each(function (o, i) {
var email = people.get_person_from_user_id(i).email;
emails.push(format_member_list_elem(email));
});
var list_html = emails.sort().reduce(function (accumulator, item) {
return accumulator + item;
}, "");
// wait for the next frame to append the list so other things can happen in
// the meanwhile.
window.requestAnimationFrame(function () {
list.append(list_html);
});
sub_settings.find('input[name="principal"]').typeahead({
source: people.get_realm_persons, // This is a function.
items: 5,
highlighter: function (item) {
var item_formatted = typeahead_helper.render_person(item);
return typeahead_helper.highlight_with_escaping(this.query, item_formatted);
},
matcher: function (item) {
var query = $.trim(this.query.toLowerCase());
if (query === '' || query === item.email) {
return false;
}
// Case-insensitive.
return (item.email.toLowerCase().indexOf(query) !== -1) ||
(item.full_name.toLowerCase().indexOf(query) !== -1);
},
sorter: function (matches) {
var current_stream = compose_state.stream_name();
return typeahead_helper.sort_recipientbox_typeahead(
this.query, matches, current_stream);
},
updater: function (item) {
return item.email;
},
});
}
exports.show_settings_for = function (node) {
var stream_id = get_stream_id(node);
var sub = stream_data.get_sub_by_id(stream_id);
var sub_settings = settings_for_sub(sub);
var sub_row = $(".subscription_settings[data-stream-id='" + stream_id + "']");
$(".subscription_settings[data-stream].show").removeClass("show");
$("#subscription_overlay .subscription_settings.show").removeClass("show");
sub_settings.addClass("show");
show_subscription_settings(sub_row);
};
function stream_home_view_clicked(e) {
var sub = get_sub_for_target(e.target);
if (!sub) {
blueslip.error('stream_home_view_clicked() fails');
return;
}
var sub_settings = settings_for_sub(sub);
var notification_checkboxes = sub_settings.find(".sub_notification_setting");
subs.toggle_home(sub);
if (sub.in_home_view) {
sub_settings.find(".mute-note").addClass("hide-mute-note");
notification_checkboxes.removeClass("muted-sub");
notification_checkboxes.find("input[type='checkbox']").removeAttr("disabled");
} else {
sub_settings.find(".mute-note").removeClass("hide-mute-note");
notification_checkboxes.addClass("muted-sub");
notification_checkboxes.find("input[type='checkbox']").attr("disabled", true);
}
}
exports.set_stream_property = function (sub, property, value) {
// TODO: Fix backend so it takes a stream id.
var stream_name = sub.name;
var sub_data = {stream: stream_name, property: property, value: value};
return channel.post({
url: '/json/subscriptions/property',
data: {subscription_data: JSON.stringify([sub_data])},
timeout: 10*1000,
});
};
function set_notification_setting_for_all_streams(notification_type, new_setting) {
_.each(stream_data.subscribed_subs(), function (sub) {
if (sub[notification_type] !== new_setting) {
exports.set_stream_property(sub, notification_type, new_setting);
}
});
}
exports.set_all_stream_desktop_notifications_to = function (new_setting) {
set_notification_setting_for_all_streams("desktop_notifications", new_setting);
};
exports.set_all_stream_audible_notifications_to = function (new_setting) {
set_notification_setting_for_all_streams("audible_notifications", new_setting);
};
function redraw_privacy_related_stuff(sub_row, sub) {
var stream_settings = settings_for_sub(sub);
var html;
sub = stream_data.add_admin_options(sub);
html = templates.render('subscription_setting_icon', sub);
sub_row.find('.icon').expectOne().replaceWith($(html));
html = templates.render('subscription_type', sub);
stream_settings.find('.subscription-type-text').expectOne().html(html);
if (sub.invite_only) {
stream_settings.find(".large-icon")
.removeClass("hash").addClass("lock")
.html("<i class='icon-vector-lock'></i>");
} else {
stream_settings.find(".large-icon")
.addClass("hash").removeClass("lock")
.html("");
}
stream_list.redraw_stream_privacy(sub.name);
}
function change_stream_privacy(e) {
e.stopPropagation();
var stream_id = $(e.target).data("stream-id");
var sub = stream_data.get_sub_by_id(stream_id);
$("#subscriptions-status").hide();
var data = {
stream_name: sub.name,
// toggle the privacy setting
is_private: !sub.invite_only,
};
channel.patch({
url: "/json/streams/" + stream_id,
data: data,
success: function () {
sub = stream_data.get_sub_by_id(stream_id);
var sub_row = $(".stream-row[data-stream-id='" + stream_id + "']");
// save new privacy settings.
sub.invite_only = !sub.invite_only;
redraw_privacy_related_stuff(sub_row, sub);
$("#stream_privacy_modal").remove();
},
error: function () {
$("#change-stream-privacy-button").text(i18n.t("Try again"));
},
});
}
function stream_desktop_notifications_clicked(e) {
var sub = get_sub_for_target(e.target);
sub.desktop_notifications = ! sub.desktop_notifications;
exports.set_stream_property(sub, 'desktop_notifications', sub.desktop_notifications);
}
function stream_audible_notifications_clicked(e) {
var sub = get_sub_for_target(e.target);
sub.audible_notifications = ! sub.audible_notifications;
exports.set_stream_property(sub, 'audible_notifications', sub.audible_notifications);
}
function stream_pin_clicked(e) {
var sub = get_sub_for_target(e.target);
if (!sub) {
blueslip.error('stream_pin_clicked() fails');
return;
}
subs.toggle_pin_to_top_stream(sub);
}
exports.change_stream_name = function (e) {
e.preventDefault();
var sub_settings = $(e.target).closest('.subscription_settings');
var stream_id = $(e.target).closest(".subscription_settings").attr("data-stream-id");
var new_name_box = sub_settings.find('.stream-name-editable');
var new_name = $.trim(new_name_box.text());
$("#subscriptions-status").hide();
channel.patch({
// Stream names might contain unsafe characters so we must encode it first.
url: "/json/streams/" + stream_id,
data: {new_name: JSON.stringify(new_name)},
success: function () {
new_name_box.val('');
ui_report.success(i18n.t("The stream has been renamed!"), $("#subscriptions-status "),
'subscriptions-status');
},
error: function (xhr) {
ui_report.error(i18n.t("Error renaming stream"), xhr,
$("#subscriptions-status"), 'subscriptions-status');
},
});
};
exports.change_stream_description = function (e) {
e.preventDefault();
var sub_settings = $(e.target).closest('.subscription_settings');
var sub = get_sub_for_target(sub_settings);
if (!sub) {
blueslip.error('change_stream_description() fails');
return;
}
var stream_id = sub.stream_id;
var description = sub_settings.find('.stream-description-editable').text().trim();
$('#subscriptions-status').hide();
channel.patch({
// Stream names might contain unsafe characters so we must encode it first.
url: '/json/streams/' + stream_id,
data: {
description: JSON.stringify(description),
},
success: function () {
// The event from the server will update the rest of the UI
ui_report.success(i18n.t("The stream description has been updated!"),
$("#subscriptions-status"), 'subscriptions-status');
},
error: function (xhr) {
ui_report.error(i18n.t("Error updating the stream description"), xhr,
$("#subscriptions-status"), 'subscriptions-status');
},
});
};
$(function () {
$("#zfilt").on("click", ".stream_sub_unsub_button", function (e) {
e.preventDefault();
e.stopPropagation();
var stream_name = narrow.stream();
if (stream_name === undefined) {
return;
}
var sub = stream_data.get_sub(stream_name);
subs.sub_or_unsub(sub);
});
$("#subscriptions_table").on("click", ".change-stream-privacy", function (e) {
var stream_id = get_stream_id(e.target);
var stream = stream_data.get_sub_by_id(stream_id);
var template_data = {
is_private: stream.can_make_public,
stream_id: stream_id,
};
var change_privacy_modal = templates.render("subscription_stream_privacy_modal", template_data);
$("#subscriptions_table").append(change_privacy_modal);
$("#change-stream-privacy-button").click(function (e) {
change_stream_privacy(e);
});
});
$("#subscriptions_table").on("click", ".close-privacy-modal", function () {
$("#stream_privacy_modal").remove();
});
$("#subscriptions_table").on("click", "#sub_setting_not_in_home_view",
stream_home_view_clicked);
$("#subscriptions_table").on("click", "#sub_desktop_notifications_setting",
stream_desktop_notifications_clicked);
$("#subscriptions_table").on("click", "#sub_audible_notifications_setting",
stream_audible_notifications_clicked);
$("#subscriptions_table").on("click", "#sub_pin_setting",
stream_pin_clicked);
$("#subscriptions_table").on("submit", ".subscriber_list_add form", function (e) {
e.preventDefault();
var settings_row = $(e.target).closest('.subscription_settings');
var sub = get_sub_for_target(settings_row);
if (!sub) {
blueslip.error('.subscriber_list_add form submit fails');
return;
}
var text_box = settings_row.find('input[name="principal"]');
var principal = $.trim(text_box.val());
// TODO: clean up this error handling
var error_elem = settings_row.find('.subscriber_list_container .alert-error');
var warning_elem = settings_row.find('.subscriber_list_container .alert-warning');
function invite_success(data) {
text_box.val('');
if (data.subscribed.hasOwnProperty(principal)) {
error_elem.addClass("hide");
warning_elem.addClass("hide");
if (people.is_current_user(principal)) {
// mark_subscribed adds the user to the member list
// TODO: We should really let the event system
// handle this, as mark_subscribed has
// lots of side effects.
stream_events.mark_subscribed(sub);
}
} else {
error_elem.addClass("hide");
warning_elem.removeClass("hide").text(i18n.t("User already subscribed"));
}
}
function invite_failure() {
warning_elem.addClass("hide");
error_elem.removeClass("hide").text(i18n.t("Could not add user to this stream"));
}
exports.invite_user_to_stream(principal, sub, invite_success, invite_failure);
});
$("#subscriptions_table").on("submit", ".subscriber_list_remove form", function (e) {
e.preventDefault();
var list_entry = $(e.target).closest("tr");
var principal = list_entry.children(".subscriber-email").text();
var settings_row = $(e.target).closest('.subscription_settings');
var sub = get_sub_for_target(settings_row);
if (!sub) {
blueslip.error('.subscriber_list_remove form submit fails');
return;
}
var error_elem = settings_row.find('.subscriber_list_container .alert-error');
var warning_elem = settings_row.find('.subscriber_list_container .alert-warning');
function removal_success(data) {
if (data.removed.length > 0) {
error_elem.addClass("hide");
warning_elem.addClass("hide");
// Remove the user from the subscriber list.
list_entry.remove();
if (people.is_current_user(principal)) {
// If you're unsubscribing yourself, mark whole
// stream entry as you being unsubscribed.
// TODO: We should really let the event system
// handle this, as mark_unsubscribed has
// lots of side effects.
stream_events.mark_unsubscribed(sub);
}
} else {
error_elem.addClass("hide");
warning_elem.removeClass("hide").text(i18n.t("User is already not subscribed"));
}
}
function removal_failure() {
warning_elem.addClass("hide");
error_elem.removeClass("hide").text(i18n.t("Error removing user from this stream"));
}
exports.remove_user_from_stream(principal, sub, removal_success,
removal_failure);
});
// This handler isn't part of the normal edit interface; it's the convenient
// checkmark in the subscriber list.
$("#subscriptions_table").on("click", ".sub_unsub_button", function (e) {
var sub = get_sub_for_target(e.target);
var stream_row = $(this).parent();
var stream_id = stream_row.attr("data-stream-id");
subs.sub_or_unsub(sub);
var sub_settings = settings_for_sub(sub);
var regular_sub_settings = sub_settings.find(".regular_subscription_settings");
if (!sub.subscribed) {
regular_sub_settings.addClass("in");
exports.show_stream_row(stream_row, true);
} else {
regular_sub_settings.removeClass("in");
}
setup_subscriptions_stream_hash(sub, stream_id);
e.preventDefault();
e.stopPropagation();
});
$("#subscriptions_table").on("click", ".stream-row", function (e) {
if ($(e.target).closest(".check, .subscription_settings").length === 0) {
exports.show_stream_row(this, true);
var stream_id = $(this).attr("data-stream-id");
var sub = stream_data.get_sub_by_id(stream_id);
setup_subscriptions_stream_hash(sub, stream_id);
}
});
$(document).on('peer_subscribe.zulip', function (e, data) {
var sub = stream_data.get_sub(data.stream_name);
var sub_row = settings_for_sub(sub);
exports.prepend_subscriber(sub_row, data.user_email);
});
$(document).on('peer_unsubscribe.zulip', function (e, data) {
var sub = stream_data.get_sub(data.stream_name);
var sub_row = settings_for_sub(sub);
var tr = sub_row.find("tr[data-subscriber-email='" +
data.user_email +
"']");
tr.remove();
});
});
return exports;
}());
if (typeof module !== 'undefined') {
module.exports = stream_edit;
}

View File

@ -6,10 +6,16 @@ var meta = {
};
var exports = {};
function settings_for_sub(sub) {
var id = parseInt(sub.stream_id, 10);
return $("#subscription_overlay .subscription_settings[data-stream-id='" + id + "']");
}
exports.show_subs_pane = {
nothing_selected: function () {
$(".nothing-selected, #stream_settings_title").show();
$("#add_new_stream_title, .settings, #stream-creation").hide();
},
settings: function () {
$(".settings, #stream_settings_title").show();
$("#add_new_stream_title, #stream-creation, .nothing-selected").hide();
},
};
function button_for_sub(sub) {
var id = parseInt(sub.stream_id, 10);
@ -76,85 +82,13 @@ function should_list_all_streams() {
return !page_params.is_zephyr_mirror_realm;
}
function set_stream_property(sub, property, value) {
// TODO: Fix backend so it takes a stream id.
var stream_name = sub.name;
var sub_data = {stream: stream_name, property: property, value: value};
return channel.post({
url: '/json/subscriptions/property',
data: {subscription_data: JSON.stringify([sub_data])},
timeout: 10*1000,
});
}
function set_notification_setting_for_all_streams(notification_type, new_setting) {
_.each(stream_data.subscribed_subs(), function (sub) {
if (sub[notification_type] !== new_setting) {
set_stream_property(sub, notification_type, new_setting);
}
});
}
exports.set_all_stream_desktop_notifications_to = function (new_setting) {
set_notification_setting_for_all_streams("desktop_notifications", new_setting);
};
exports.set_all_stream_audible_notifications_to = function (new_setting) {
set_notification_setting_for_all_streams("audible_notifications", new_setting);
};
function get_stream_id(target) {
if (target.constructor !== jQuery) {
target = $(target);
}
return target.closest(".stream-row, .subscription_settings").attr("data-stream-id");
}
function get_sub_for_target(target) {
var stream_id = get_stream_id(target);
if (!stream_id) {
blueslip.error('Cannot find stream id for target');
return;
}
var sub = stream_data.get_sub_by_id(stream_id);
if (!sub) {
blueslip.error('get_sub_for_target() failed id lookup: ' + stream_id);
return;
}
return sub;
}
function stream_home_view_clicked(e) {
var sub = get_sub_for_target(e.target);
if (!sub) {
blueslip.error('stream_home_view_clicked() fails');
return;
}
var sub_settings = settings_for_sub(sub);
var notification_checkboxes = sub_settings.find(".sub_notification_setting");
subs.toggle_home(sub);
if (sub.in_home_view) {
sub_settings.find(".mute-note").addClass("hide-mute-note");
notification_checkboxes.removeClass("muted-sub");
notification_checkboxes.find("input[type='checkbox']").removeAttr("disabled");
} else {
sub_settings.find(".mute-note").removeClass("hide-mute-note");
notification_checkboxes.addClass("muted-sub");
notification_checkboxes.find("input[type='checkbox']").attr("disabled", true);
}
}
exports.toggle_home = function (sub) {
stream_muting.update_in_home_view(sub, ! sub.in_home_view);
set_stream_property(sub, 'in_home_view', sub.in_home_view);
stream_edit.set_stream_property(sub, 'in_home_view', sub.in_home_view);
};
exports.toggle_pin_to_top_stream = function (sub) {
set_stream_property(sub, 'pin_to_top', !sub.pin_to_top);
stream_edit.set_stream_property(sub, 'pin_to_top', !sub.pin_to_top);
};
exports.update_stream_name = function (sub, new_name) {
@ -166,9 +100,7 @@ exports.update_stream_name = function (sub, new_name) {
stream_list.rename_stream(sub, new_name);
// Update the stream settings
var sub_settings = settings_for_sub(stream_data.get_sub_by_id(stream_id));
sub_settings.find(".email-address").text(sub.email_address);
sub_settings.find(".stream-name-editable").text(new_name);
stream_edit.update_stream_name(sub, new_name);
// Update the subscriptions page
var sub_row = $(".stream-row[data-stream-id='" + stream_id + "']");
@ -187,35 +119,12 @@ exports.update_stream_description = function (sub, description) {
sub_row.find(".description").html(sub.rendered_description);
// Update stream settings
var stream_settings = settings_for_sub(sub);
stream_settings.find('input.description').val(sub.description);
stream_settings.find('.stream-description-editable').html(sub.rendered_description);
stream_edit.update_stream_description(sub);
};
function stream_desktop_notifications_clicked(e) {
var sub = get_sub_for_target(e.target);
sub.desktop_notifications = ! sub.desktop_notifications;
set_stream_property(sub, 'desktop_notifications', sub.desktop_notifications);
}
function stream_audible_notifications_clicked(e) {
var sub = get_sub_for_target(e.target);
sub.audible_notifications = ! sub.audible_notifications;
set_stream_property(sub, 'audible_notifications', sub.audible_notifications);
}
function stream_pin_clicked(e) {
var sub = get_sub_for_target(e.target);
if (!sub) {
blueslip.error('stream_pin_clicked() fails');
return;
}
exports.toggle_pin_to_top_stream(sub);
}
exports.set_color = function (stream_id, color) {
var sub = stream_data.get_sub_by_id(stream_id);
set_stream_property(sub, 'color', color);
stream_edit.set_stream_property(sub, 'color', color);
};
exports.rerender_subscribers_count = function (sub) {
@ -271,23 +180,6 @@ function add_sub_to_table(sub) {
}
}
function format_member_list_elem(email) {
var person = people.get_by_email(email);
return templates.render('stream_member_list_entry',
{name: person.full_name, email: email,
displaying_for_admin: page_params.is_admin});
}
function get_subscriber_list(sub_row) {
var id = sub_row.data("stream-id");
return $('.subscription_settings[data-stream-id="' + id + '"] .subscriber-list');
}
function prepend_subscriber(sub_row, email) {
var list = get_subscriber_list(sub_row);
list.prepend(format_member_list_elem(email));
}
exports.remove_stream = function (stream_id) {
// It is possible that row is empty when we deactivate a
// stream, but we let jQuery silently handle that.
@ -295,85 +187,7 @@ exports.remove_stream = function (stream_id) {
row.remove();
};
function show_subscription_settings(sub_row) {
var stream_id = sub_row.data("stream-id");
var sub = stream_data.get_sub_by_id(stream_id);
var sub_settings = settings_for_sub(sub);
var alerts = sub_settings
.find('.subscriber_list_container')
.find('.alert-warning, .alert-error');
var colorpicker = sub_settings.find('.colorpicker');
var color = stream_data.get_color(sub.name);
stream_color.set_colorpicker_color(colorpicker, color);
if (!sub.render_subscribers) {
return;
}
// fetch subscriber list from memory.
var list = get_subscriber_list(sub_settings);
alerts.addClass("hide");
list.empty();
var emails = [];
sub.subscribers.each(function (o, i) {
var email = people.get_person_from_user_id(i).email;
emails.push(format_member_list_elem(email));
});
var list_html = emails.sort().reduce(function (accumulator, item) {
return accumulator + item;
}, "");
// wait for the next frame to append the list so other things can happen in
// the meanwhile.
window.requestAnimationFrame(function () {
list.append(list_html);
});
sub_settings.find('input[name="principal"]').typeahead({
source: people.get_realm_persons, // This is a function.
items: 5,
highlighter: function (item) {
var item_formatted = typeahead_helper.render_person(item);
return typeahead_helper.highlight_with_escaping(this.query, item_formatted);
},
matcher: function (item) {
var query = $.trim(this.query.toLowerCase());
if (query === '' || query === item.email) {
return false;
}
// Case-insensitive.
return (item.email.toLowerCase().indexOf(query) !== -1) ||
(item.full_name.toLowerCase().indexOf(query) !== -1);
},
sorter: function (matches) {
var current_stream = compose_state.stream_name();
return typeahead_helper.sort_recipientbox_typeahead(
this.query, matches, current_stream);
},
updater: function (item) {
return item.email;
},
});
}
exports.show_settings_for = function (stream_id) {
var sub = stream_data.get_sub_by_id(stream_id);
var sub_settings = settings_for_sub(sub);
var sub_row = $(".subscription_settings[data-stream-id='" + stream_id + "']");
$(".subscription_settings[data-stream].show").removeClass("show");
$("#subscription_overlay .subscription_settings.show").removeClass("show");
sub_settings.addClass("show");
show_subscription_settings(sub_row);
};
exports.update_settings_for_subscribed = function (sub) {
var stream_settings = settings_for_sub(sub);
var button = button_for_sub(sub);
var settings_button = settings_button_for_sub(sub).removeClass("unsubscribed");
@ -382,19 +196,14 @@ exports.update_settings_for_subscribed = function (sub) {
button.toggleClass("checked");
settings_button.text(i18n.t("Unsubscribe"));
// Add the user to the member list if they're currently
// viewing the members of this stream
if (sub.render_subscribers && stream_settings.hasClass('in')) {
prepend_subscriber(stream_settings,
people.my_current_email());
}
stream_edit.add_me_to_member_list(sub);
} else {
add_sub_to_table(sub);
}
// Display the swatch and subscription stream_settings
var sub_row = stream_settings.closest('.stream-row');
sub_row.find(".regular_subscription_settings").addClass('in');
stream_edit.show_sub(sub);
};
exports.update_settings_for_unsubscribed = function (sub) {
@ -404,16 +213,10 @@ exports.update_settings_for_unsubscribed = function (sub) {
button.toggleClass("checked");
settings_button.text(i18n.t("Subscribe"));
var stream_settings = settings_for_sub(sub);
if (stream_settings.hasClass('in')) {
stream_settings.collapse('hide');
}
exports.rerender_subscribers_count(sub);
// Hide the swatch and subscription settings
var sub_row = stream_settings.closest('.stream-row');
sub_row.find(".regular_subscription_settings").removeClass('in');
stream_edit.collapse(sub);
row_for_stream_id(subs.stream_id).attr("data-temp-view", true);
};
@ -518,63 +321,6 @@ function actually_filter_streams() {
exports.filter_table({ input: query, subscribed_only: subscribed_only });
}
function redraw_privacy_related_stuff(sub_row, sub) {
var stream_settings = settings_for_sub(sub);
var html;
sub = stream_data.add_admin_options(sub);
html = templates.render('subscription_setting_icon', sub);
sub_row.find('.icon').expectOne().replaceWith($(html));
html = templates.render('subscription_type', sub);
stream_settings.find('.subscription-type-text').expectOne().html(html);
if (sub.invite_only) {
stream_settings.find(".large-icon")
.removeClass("hash").addClass("lock")
.html("<i class='icon-vector-lock'></i>");
} else {
stream_settings.find(".large-icon")
.addClass("hash").removeClass("lock")
.html("");
}
stream_list.redraw_stream_privacy(sub.name);
}
function change_stream_privacy(e) {
e.stopPropagation();
var stream_id = $(e.target).data("stream-id");
var sub = stream_data.get_sub_by_id(stream_id);
$("#subscriptions-status").hide();
var data = {
stream_name: sub.name,
// toggle the privacy setting
is_private: !sub.invite_only,
};
channel.patch({
url: "/json/streams/" + stream_id,
data: data,
success: function () {
sub = stream_data.get_sub_by_id(stream_id);
var sub_row = $(".stream-row[data-stream-id='" + stream_id + "']");
// save new privacy settings.
sub.invite_only = !sub.invite_only;
redraw_privacy_related_stuff(sub_row, sub);
$("#stream_privacy_modal").remove();
},
error: function () {
$("#change-stream-privacy-button").text(i18n.t("Try again"));
},
});
}
var filter_streams = _.throttle(actually_filter_streams, 50);
exports.setup_page = function (callback) {
@ -847,88 +593,6 @@ exports.new_stream_clicked = function () {
stream_create.new_stream_clicked(stream);
};
exports.invite_user_to_stream = function (user_email, sub, success, failure) {
// TODO: use stream_id when backend supports it
var stream_name = sub.name;
return channel.post({
url: "/json/users/me/subscriptions",
data: {subscriptions: JSON.stringify([{name: stream_name}]),
principals: JSON.stringify([user_email])},
success: success,
error: failure,
});
};
exports.remove_user_from_stream = function (user_email, sub, success, failure) {
// TODO: use stream_id when backend supports it
var stream_name = sub.name;
return channel.del({
url: "/json/users/me/subscriptions",
data: {subscriptions: JSON.stringify([stream_name]),
principals: JSON.stringify([user_email])},
success: success,
error: failure,
});
};
exports.change_stream_description = function (e) {
e.preventDefault();
var sub_settings = $(e.target).closest('.subscription_settings');
var sub = get_sub_for_target(sub_settings);
if (!sub) {
blueslip.error('change_stream_description() fails');
return;
}
var stream_id = sub.stream_id;
var description = sub_settings.find('.stream-description-editable').text().trim();
$('#subscriptions-status').hide();
channel.patch({
// Stream names might contain unsafe characters so we must encode it first.
url: '/json/streams/' + stream_id,
data: {
description: JSON.stringify(description),
},
success: function () {
// The event from the server will update the rest of the UI
ui_report.success(i18n.t("The stream description has been updated!"),
$("#subscriptions-status"), 'subscriptions-status');
},
error: function (xhr) {
ui_report.error(i18n.t("Error updating the stream description"), xhr,
$("#subscriptions-status"), 'subscriptions-status');
},
});
};
exports.change_stream_name = function (e) {
e.preventDefault();
var sub_settings = $(e.target).closest('.subscription_settings');
var stream_id = $(e.target).closest(".subscription_settings").attr("data-stream-id");
var new_name_box = sub_settings.find('.stream-name-editable');
var new_name = $.trim(new_name_box.text());
$("#subscriptions-status").hide();
channel.patch({
// Stream names might contain unsafe characters so we must encode it first.
url: "/json/streams/" + stream_id,
data: {new_name: JSON.stringify(new_name)},
success: function () {
new_name_box.val('');
ui_report.success(i18n.t("The stream has been renamed!"), $("#subscriptions-status "),
'subscriptions-status');
},
error: function (xhr) {
ui_report.error(i18n.t("Error renaming stream"), xhr,
$("#subscriptions-status"), 'subscriptions-status');
},
});
};
exports.sub_or_unsub = function (sub) {
if (sub.subscribed) {
ajaxUnsubscribe(sub);
@ -947,17 +611,6 @@ $(function () {
// when new messages come in, but it's fairly quick.
stream_list.build_stream_list();
var show_subs_pane = {
nothing_selected: function () {
$(".nothing-selected, #stream_settings_title").show();
$("#add_new_stream_title, .settings, #stream-creation").hide();
},
settings: function () {
$(".settings, #stream_settings_title").show();
$("#add_new_stream_title, #stream-creation, .nothing-selected").hide();
},
};
$("#subscriptions_table").on("click", "#create_stream_button", function (e) {
e.preventDefault();
exports.new_stream_clicked();
@ -969,7 +622,7 @@ $(function () {
// click; this fixes an issue where hitting "enter" would
// trigger this code path due to bootstrap magic.
if (e.clientY !== 0) {
show_subs_pane.nothing_selected();
exports.show_subs_pane.nothing_selected();
}
});
@ -987,58 +640,6 @@ $(function () {
selectText(this);
});
function setup_subscriptions_stream_hash(sub, stream_id) {
exports.change_state.prevent_once();
window.location.hash = "#streams" + "/" +
stream_id + "/" +
hash_util.encodeHashComponent(sub.name);
}
function show_stream_row(node, show_settings) {
$(".display-type #add_new_stream_title").hide();
$(".display-type #stream_settings_title, .right .settings").show();
$(".stream-row.active").removeClass("active");
if (show_settings) {
show_subs_pane.settings();
$(node).addClass("active");
exports.show_settings_for(get_stream_id(node));
} else {
show_subs_pane.nothing_selected();
}
}
$("#subscriptions_table").on("click", ".sub_unsub_button", function (e) {
var sub = get_sub_for_target(e.target);
var stream_row = $(this).parent();
var stream_id = stream_row.attr("data-stream-id");
exports.sub_or_unsub(sub);
var sub_settings = settings_for_sub(sub);
var regular_sub_settings = sub_settings.find(".regular_subscription_settings");
if (!sub.subscribed) {
regular_sub_settings.addClass("in");
show_stream_row(stream_row, true);
} else {
regular_sub_settings.removeClass("in");
}
setup_subscriptions_stream_hash(sub, stream_id);
e.preventDefault();
e.stopPropagation();
});
$("#zfilt").on("click", ".stream_sub_unsub_button", function (e) {
e.preventDefault();
e.stopPropagation();
var stream_name = narrow.stream();
if (stream_name === undefined) {
return;
}
var sub = stream_data.get_sub(stream_name);
exports.sub_or_unsub(sub);
});
$('.empty_feed_sub_unsub').click(function (e) {
e.preventDefault();
@ -1072,142 +673,17 @@ $(function () {
control.prop("checked", ! control.prop("checked"));
}
});
$("#subscriptions_table").on("click", "#sub_setting_not_in_home_view", stream_home_view_clicked);
$("#subscriptions_table").on("click", "#sub_desktop_notifications_setting",
stream_desktop_notifications_clicked);
$("#subscriptions_table").on("click", "#sub_audible_notifications_setting",
stream_audible_notifications_clicked);
$("#subscriptions_table").on("click", "#sub_pin_setting",
stream_pin_clicked);
$("#subscriptions_table").on("submit", ".subscriber_list_add form", function (e) {
e.preventDefault();
var settings_row = $(e.target).closest('.subscription_settings');
var sub = get_sub_for_target(settings_row);
if (!sub) {
blueslip.error('.subscriber_list_add form submit fails');
return;
}
var text_box = settings_row.find('input[name="principal"]');
var principal = $.trim(text_box.val());
// TODO: clean up this error handling
var error_elem = settings_row.find('.subscriber_list_container .alert-error');
var warning_elem = settings_row.find('.subscriber_list_container .alert-warning');
function invite_success(data) {
text_box.val('');
if (data.subscribed.hasOwnProperty(principal)) {
error_elem.addClass("hide");
warning_elem.addClass("hide");
if (people.is_current_user(principal)) {
// mark_subscribed adds the user to the member list
// TODO: We should really let the event system
// handle this, as mark_subscribed has
// lots of side effects.
stream_events.mark_subscribed(sub);
}
} else {
error_elem.addClass("hide");
warning_elem.removeClass("hide").text(i18n.t("User already subscribed"));
}
}
function invite_failure() {
warning_elem.addClass("hide");
error_elem.removeClass("hide").text(i18n.t("Could not add user to this stream"));
}
exports.invite_user_to_stream(principal, sub, invite_success, invite_failure);
});
$("#subscriptions_table").on("click", ".stream-row", function (e) {
if ($(e.target).closest(".check, .subscription_settings").length === 0) {
show_stream_row(this, true);
var stream_id = $(this).attr("data-stream-id");
var sub = stream_data.get_sub_by_id(stream_id);
setup_subscriptions_stream_hash(sub, stream_id);
}
});
(function defocus_sub_settings() {
var sel = ".search-container, .streams-list, .subscriptions-header";
$("#subscriptions_table").on("click", sel, function (e) {
if ($(e.target).is(sel)) {
show_stream_row(this, false);
stream_edit.show_stream_row(this, false);
}
});
}());
$("#subscriptions_table").on("submit", ".subscriber_list_remove form", function (e) {
e.preventDefault();
var list_entry = $(e.target).closest("tr");
var principal = list_entry.children(".subscriber-email").text();
var settings_row = $(e.target).closest('.subscription_settings');
var sub = get_sub_for_target(settings_row);
if (!sub) {
blueslip.error('.subscriber_list_remove form submit fails');
return;
}
var error_elem = settings_row.find('.subscriber_list_container .alert-error');
var warning_elem = settings_row.find('.subscriber_list_container .alert-warning');
function removal_success(data) {
if (data.removed.length > 0) {
error_elem.addClass("hide");
warning_elem.addClass("hide");
// Remove the user from the subscriber list.
list_entry.remove();
if (people.is_current_user(principal)) {
// If you're unsubscribing yourself, mark whole
// stream entry as you being unsubscribed.
// TODO: We should really let the event system
// handle this, as mark_unsubscribed has
// lots of side effects.
stream_events.mark_unsubscribed(sub);
}
} else {
error_elem.addClass("hide");
warning_elem.removeClass("hide").text(i18n.t("User is already not subscribed"));
}
}
function removal_failure() {
warning_elem.addClass("hide");
error_elem.removeClass("hide").text(i18n.t("Error removing user from this stream"));
}
exports.remove_user_from_stream(principal, sub, removal_success,
removal_failure);
});
$("#subscriptions_table").on("click", ".change-stream-privacy", function (e) {
var stream_id = get_stream_id(e.target);
var stream = stream_data.get_sub_by_id(stream_id);
var template_data = {
is_private: stream.can_make_public,
stream_id: stream_id,
};
var change_privacy_modal = templates.render("subscription_stream_privacy_modal", template_data);
$("#subscriptions_table").append(change_privacy_modal);
$("#change-stream-privacy-button").click(function (e) {
change_stream_privacy(e);
});
});
$("#subscriptions_table").on("click", ".close-privacy-modal", function () {
$("#stream_privacy_modal").remove();
});
$("#subscriptions_table").on("hide", ".subscription_settings", function (e) {
var sub_arrow = $(e.target).closest('.stream-row').find('.sub_arrow i');
sub_arrow.removeClass('icon-vector-chevron-up');
@ -1217,18 +693,11 @@ $(function () {
$(document).on('peer_subscribe.zulip', function (e, data) {
var sub = stream_data.get_sub(data.stream_name);
exports.rerender_subscribers_count(sub);
var sub_row = settings_for_sub(sub);
prepend_subscriber(sub_row, data.user_email);
});
$(document).on('peer_unsubscribe.zulip', function (e, data) {
var sub = stream_data.get_sub(data.stream_name);
exports.rerender_subscribers_count(sub);
var sub_row = settings_for_sub(sub);
var tr = sub_row.find("tr[data-subscriber-email='" +
data.user_email +
"']");
tr.remove();
});
function subscriptions_close_modal() {

View File

@ -137,6 +137,9 @@ def find_edges_to_remove(graph, methods):
('compose_actions', 'resize'),
('settings_streams', 'stream_data'),
('drafts', 'hashchange'),
('settings_notifications', 'stream_edit'),
('compose', 'stream_edit'),
('subs', 'stream_edit'),
] # type: List[Edge]
def is_exempt(edge):
@ -152,6 +155,7 @@ def find_edges_to_remove(graph, methods):
return edge in EXEMPT_EDGES
APPROVED_CUTS = [
('stream_edit', 'stream_events'),
('unread_ui', 'pointer'),
('typing_events', 'narrow'),
('echo', 'message_events'),
@ -199,6 +203,7 @@ def find_edges_to_remove(graph, methods):
('muting_ui', 'stream_popover'),
('popovers', 'stream_popover'),
('topic_list', 'stream_popover'),
('stream_edit', 'subs'),
('topic_list', 'narrow'),
('stream_list', 'narrow'),
('stream_list', 'pm_list'),

View File

@ -15,6 +15,7 @@ GENERIC_KEYWORDS = [
'error',
'expanded',
'hide',
'in',
'show',
'notdisplayed',
'popover',

View File

@ -868,6 +868,7 @@ JS_SPECS = {
'js/stream_muting.js',
'js/stream_events.js',
'js/stream_create.js',
'js/stream_edit.js',
'js/subs.js',
'js/message_edit.js',
'js/condense.js',