mirror of
https://github.com/zulip/zulip.git
synced 2025-11-04 14:03:30 +00:00
We want to mostly deprecate this function (see the comment I added), so I gave it a more specific name. Ideally I'd just fix `stream_create`, but it does use this function in a couple places, and it's helpful to reuse the same sort here. In one place stream_create actually unshifts the "me" user back to the top of the list, which makes sense for its use case.
461 lines
16 KiB
JavaScript
461 lines
16 KiB
JavaScript
const render_announce_stream_docs = require('../templates/announce_stream_docs.hbs');
|
|
const render_new_stream_users = require('../templates/new_stream_users.hbs');
|
|
const render_subscription_invites_warning_modal = require('../templates/subscription_invites_warning_modal.hbs');
|
|
|
|
let created_stream;
|
|
|
|
exports.reset_created_stream = function () {
|
|
created_stream = undefined;
|
|
};
|
|
|
|
exports.set_name = function (stream) {
|
|
created_stream = stream;
|
|
};
|
|
|
|
exports.get_name = function () {
|
|
return created_stream;
|
|
};
|
|
|
|
const stream_subscription_error = (function () {
|
|
const self = {};
|
|
|
|
self.report_no_subs_to_stream = function () {
|
|
$("#stream_subscription_error").text(i18n.t("You cannot create a stream with no subscribers!"));
|
|
$("#stream_subscription_error").show();
|
|
};
|
|
|
|
self.cant_create_stream_without_susbscribing = function () {
|
|
$("#stream_subscription_error").text(i18n.t("You must be an organization administrator to create a stream without subscribing."));
|
|
$("#stream_subscription_error").show();
|
|
};
|
|
|
|
self.clear_errors = function () {
|
|
$("#stream_subscription_error").hide();
|
|
};
|
|
|
|
return self;
|
|
|
|
}());
|
|
|
|
const stream_name_error = (function () {
|
|
const self = {};
|
|
|
|
self.report_already_exists = function () {
|
|
$("#stream_name_error").text(i18n.t("A stream with this name already exists"));
|
|
$("#stream_name_error").show();
|
|
};
|
|
|
|
self.clear_errors = function () {
|
|
$("#stream_name_error").hide();
|
|
};
|
|
|
|
self.report_empty_stream = function () {
|
|
$("#stream_name_error").text(i18n.t("A stream needs to have a name"));
|
|
$("#stream_name_error").show();
|
|
};
|
|
|
|
self.select = function () {
|
|
$("#create_stream_name").focus().select();
|
|
};
|
|
|
|
self.pre_validate = function (stream_name) {
|
|
// Don't worry about empty strings...we just want to call this
|
|
// to warn users early before they start doing too much work
|
|
// after they make the effort to type in a stream name. (The
|
|
// use case here is that I go to create a stream, only to find
|
|
// out it already exists, and I was just too lazy to look at
|
|
// the public streams that I'm not subscribed to yet. Once I
|
|
// realize the stream already exists, I may want to cancel.)
|
|
if (stream_name && stream_data.get_sub(stream_name)) {
|
|
self.report_already_exists();
|
|
return;
|
|
}
|
|
|
|
self.clear_errors();
|
|
};
|
|
|
|
self.validate_for_submit = function (stream_name) {
|
|
if (!stream_name) {
|
|
self.report_empty_stream();
|
|
self.select();
|
|
return false;
|
|
}
|
|
|
|
if (stream_data.get_sub(stream_name)) {
|
|
self.report_already_exists();
|
|
self.select();
|
|
return false;
|
|
}
|
|
|
|
// If we got this far, then we think we have a new unique stream
|
|
// name, so we'll submit to the server. (It's still plausible,
|
|
// however, that there's some invite-only stream that we don't
|
|
// know about locally that will cause a name collision.)
|
|
return true;
|
|
};
|
|
|
|
return self;
|
|
}());
|
|
|
|
function ajaxSubscribeForCreation(stream_name, description, principals, invite_only,
|
|
is_announcement_only, announce, history_public_to_subscribers) {
|
|
// Subscribe yourself and possible other people to a new stream.
|
|
return channel.post({
|
|
url: "/json/users/me/subscriptions",
|
|
data: {subscriptions: JSON.stringify([{name: stream_name,
|
|
description: description}]),
|
|
principals: JSON.stringify(principals),
|
|
invite_only: JSON.stringify(invite_only),
|
|
is_announcement_only: JSON.stringify(is_announcement_only),
|
|
announce: JSON.stringify(announce),
|
|
history_public_to_subscribers: JSON.stringify(history_public_to_subscribers),
|
|
},
|
|
success: function () {
|
|
$("#create_stream_name").val("");
|
|
$("#create_stream_description").val("");
|
|
ui_report.success(i18n.t("Stream successfully created!"), $(".stream_create_info"));
|
|
loading.destroy_indicator($('#stream_creating_indicator'));
|
|
// The rest of the work is done via the subscribe event we will get
|
|
},
|
|
error: function (xhr) {
|
|
const msg = JSON.parse(xhr.responseText).msg;
|
|
if (msg.indexOf('access') >= 0) {
|
|
// If we can't access the stream, we can safely assume it's
|
|
// a duplicate stream that we are not invited to.
|
|
stream_name_error.report_already_exists(stream_name);
|
|
stream_name_error.select();
|
|
}
|
|
|
|
ui_report.error(i18n.t("Error creating stream"), xhr, $(".stream_create_info"));
|
|
loading.destroy_indicator($('#stream_creating_indicator'));
|
|
},
|
|
});
|
|
}
|
|
|
|
// Within the new stream modal...
|
|
function update_announce_stream_state() {
|
|
|
|
// If there is no notifications_stream, we simply hide the widget.
|
|
if (!page_params.notifications_stream) {
|
|
$('#announce-new-stream').hide();
|
|
return;
|
|
}
|
|
|
|
// If the stream is invite only, disable the "Announce stream" option.
|
|
// Otherwise enable it.
|
|
const announce_stream_checkbox = $('#announce-new-stream input');
|
|
const announce_stream_label = $('#announce-new-stream');
|
|
let disable_it = false;
|
|
const privacy_type = $('input:radio[name=privacy]:checked').val();
|
|
const is_invite_only = privacy_type === "invite-only" || privacy_type === "invite-only-public-history";
|
|
announce_stream_label.removeClass("control-label-disabled");
|
|
|
|
if (is_invite_only) {
|
|
disable_it = true;
|
|
announce_stream_checkbox.prop('checked', false);
|
|
announce_stream_label.addClass("control-label-disabled");
|
|
}
|
|
|
|
announce_stream_checkbox.prop('disabled', disable_it);
|
|
$('#announce-new-stream').show();
|
|
}
|
|
|
|
function get_principals() {
|
|
return _.map(
|
|
$("#stream_creation_form input:checkbox[name=user]:checked"),
|
|
function (elem) {
|
|
return $(elem).val();
|
|
}
|
|
);
|
|
}
|
|
|
|
function create_stream() {
|
|
const stream_name = $.trim($("#create_stream_name").val());
|
|
const description = $.trim($("#create_stream_description").val());
|
|
const privacy_setting = $('#stream_creation_form input[name=privacy]:checked').val();
|
|
const is_announcement_only = $('#stream_creation_form input[name=is-announcement-only]').prop('checked');
|
|
const principals = get_principals();
|
|
|
|
let invite_only;
|
|
let history_public_to_subscribers;
|
|
|
|
if (privacy_setting === 'invite-only') {
|
|
invite_only = true;
|
|
history_public_to_subscribers = false;
|
|
} else if (privacy_setting === 'invite-only-public-history') {
|
|
invite_only = true;
|
|
history_public_to_subscribers = true;
|
|
} else {
|
|
invite_only = false;
|
|
history_public_to_subscribers = true;
|
|
}
|
|
|
|
created_stream = stream_name;
|
|
|
|
const announce = !!page_params.notifications_stream &&
|
|
$('#announce-new-stream input').prop('checked');
|
|
|
|
// Even though we already check to make sure that while typing the user cannot enter
|
|
// newline characters (by pressing the enter key) it would still be possible to copy
|
|
// and paste over a description with newline characters in it. Prevent that.
|
|
if (description.indexOf('\n') !== -1) {
|
|
ui_report.message(i18n.t("The stream description cannot contain newline characters."), $(".stream_create_info"), 'alert-error');
|
|
return;
|
|
}
|
|
|
|
loading.make_indicator($('#stream_creating_indicator'), {text: i18n.t('Creating stream...')});
|
|
|
|
ajaxSubscribeForCreation(
|
|
stream_name,
|
|
description,
|
|
principals,
|
|
invite_only,
|
|
is_announcement_only,
|
|
announce,
|
|
history_public_to_subscribers
|
|
);
|
|
}
|
|
|
|
exports.new_stream_clicked = function (stream_name) {
|
|
// this changes the tab switcher (settings/preview) which isn't necessary
|
|
// to a add new stream title.
|
|
$(".display-type #add_new_stream_title").show();
|
|
$(".display-type #stream_settings_title").hide();
|
|
|
|
$(".stream-row.active").removeClass("active");
|
|
|
|
$("#stream_settings_title, .subscriptions-container .settings, .nothing-selected").hide();
|
|
$("#stream-creation, #add_new_stream_title").show();
|
|
|
|
if (stream_name !== '') {
|
|
$('#create_stream_name').val(stream_name);
|
|
}
|
|
exports.show_new_stream_modal();
|
|
|
|
// at less than 700px we have a @media query that when you tap the
|
|
// .create_stream_button, the stream prompt slides in. However, when you
|
|
// focus the button on that page, the entire app view jumps over to
|
|
// the other tab, and the animation breaks.
|
|
// it is unclear whether this is a browser bug or "feature", however what
|
|
// is clear is that this shoudn't be touched unless you're also changing
|
|
// the mobile @media query at 700px.
|
|
if (window.innerWidth > 700) {
|
|
$('#create_stream_name').focus();
|
|
}
|
|
};
|
|
|
|
function clear_error_display() {
|
|
stream_name_error.clear_errors();
|
|
$(".stream_create_info").hide();
|
|
stream_subscription_error.clear_errors();
|
|
}
|
|
|
|
exports.show_new_stream_modal = function () {
|
|
$("#stream-creation").removeClass("hide");
|
|
$(".right .settings").hide();
|
|
|
|
const all_users = people.get_people_for_stream_create();
|
|
// Add current user on top of list
|
|
all_users.unshift(people.get_person_from_user_id(page_params.user_id));
|
|
const html = render_new_stream_users({
|
|
users: all_users,
|
|
streams: stream_data.get_streams_for_settings_page(),
|
|
is_admin: page_params.is_admin,
|
|
});
|
|
|
|
const container = $('#people_to_add');
|
|
container.html(html);
|
|
exports.create_handlers_for_users(container);
|
|
|
|
// Make the options default to the same each time:
|
|
// public, "announce stream" on.
|
|
$('#make-invite-only input:radio[value=public]').prop('checked', true);
|
|
|
|
if (page_params.notifications_stream) {
|
|
$('#announce-new-stream').show();
|
|
$('#announce-new-stream input').prop('disabled', false);
|
|
$('#announce-new-stream input').prop('checked', true);
|
|
} else {
|
|
$('#announce-new-stream').hide();
|
|
}
|
|
clear_error_display();
|
|
|
|
$("#stream-checkboxes label.checkbox").on('change', function (e) {
|
|
const elem = $(this);
|
|
const stream_id = elem.attr('data-stream-id');
|
|
const checked = elem.find('input').prop('checked');
|
|
const subscriber_ids = stream_data.get_sub_by_id(stream_id).subscribers;
|
|
|
|
$('#user-checkboxes label.checkbox').each(function () {
|
|
const user_elem = $(this);
|
|
const user_id = user_elem.attr('data-user-id');
|
|
|
|
if (subscriber_ids.has(user_id)) {
|
|
user_elem.find('input').prop('checked', checked);
|
|
}
|
|
});
|
|
|
|
e.preventDefault();
|
|
});
|
|
};
|
|
|
|
exports.create_handlers_for_users = function (container) {
|
|
// container should be $('#people_to_add')...see caller to verify
|
|
container.on('change', '#user-checkboxes input', update_announce_stream_state);
|
|
|
|
// 'Check all' and 'Uncheck all' visible users
|
|
container.on('click', '.subs_set_all_users', function (e) {
|
|
$('#user-checkboxes .checkbox').each(function (idx, li) {
|
|
if (li.style.display !== "none") {
|
|
$(li.firstElementChild).prop('checked', true);
|
|
}
|
|
});
|
|
e.preventDefault();
|
|
update_announce_stream_state();
|
|
});
|
|
|
|
container.on('click', '.subs_unset_all_users', function (e) {
|
|
$('#user-checkboxes .checkbox').each(function (idx, li) {
|
|
if (li.style.display !== "none") {
|
|
// The first checkbox is the one for ourself; this is the code path for:
|
|
// `stream_subscription_error.cant_create_stream_without_susbscribing`
|
|
if (idx === 0 && !page_params.is_admin) {
|
|
return;
|
|
}
|
|
$(li.firstElementChild).prop('checked', false);
|
|
}
|
|
});
|
|
e.preventDefault();
|
|
update_announce_stream_state();
|
|
});
|
|
|
|
container.on('click', '#copy-from-stream-expand-collapse', function (e) {
|
|
$('#stream-checkboxes').toggle();
|
|
$("#copy-from-stream-expand-collapse .toggle").toggleClass('fa-caret-right fa-caret-down');
|
|
e.preventDefault();
|
|
});
|
|
|
|
// Search People or Streams
|
|
container.on('input', '.add-user-list-filter', function (e) {
|
|
const user_list = $(".add-user-list-filter");
|
|
if (user_list === 0) {
|
|
return;
|
|
}
|
|
const search_term = user_list.expectOne().val().trim();
|
|
const search_terms = search_term.toLowerCase().split(",");
|
|
|
|
(function filter_user_checkboxes() {
|
|
const user_labels = $("#user-checkboxes label.add-user-label");
|
|
|
|
if (search_term === '') {
|
|
user_labels.css({display: 'block'});
|
|
return;
|
|
}
|
|
|
|
const users = people.get_people_for_stream_create();
|
|
const filtered_users = people.filter_people_by_search_terms(users, search_terms);
|
|
|
|
// Be careful about modifying the follow code. A naive implementation
|
|
// will work very poorly with a large user population (~1000 users).
|
|
//
|
|
// I tested using: `./manage.py populate_db --extra-users 3500`
|
|
//
|
|
// This would break the previous implementation, whereas the new
|
|
// implementation is merely sluggish.
|
|
user_labels.each(function () {
|
|
const elem = $(this);
|
|
const user_id = elem.attr('data-user-id');
|
|
const user_checked = filtered_users.has(user_id);
|
|
const display = user_checked ? "block" : "none";
|
|
elem.css({display: display});
|
|
});
|
|
}());
|
|
|
|
e.preventDefault();
|
|
});
|
|
};
|
|
|
|
|
|
exports.set_up_handlers = function () {
|
|
const container = $('#stream-creation').expectOne();
|
|
|
|
container.on('change', '#make-invite-only input', update_announce_stream_state);
|
|
|
|
container.on("submit", "#stream_creation_form", function (e) {
|
|
e.preventDefault();
|
|
clear_error_display();
|
|
|
|
const stream_name = $.trim($("#create_stream_name").val());
|
|
const name_ok = stream_name_error.validate_for_submit(stream_name);
|
|
|
|
if (!name_ok) {
|
|
return;
|
|
}
|
|
|
|
const principals = get_principals();
|
|
if (principals.length === 0) {
|
|
stream_subscription_error.report_no_subs_to_stream();
|
|
return;
|
|
}
|
|
if (principals.indexOf(people.my_current_email()) < 0 && !page_params.is_admin) {
|
|
stream_subscription_error.cant_create_stream_without_susbscribing();
|
|
return;
|
|
}
|
|
|
|
if (principals.length >= 50) {
|
|
const invites_warning_modal = render_subscription_invites_warning_modal({
|
|
stream_name: stream_name,
|
|
count: principals.length,
|
|
});
|
|
$('#stream-creation').append(invites_warning_modal);
|
|
} else {
|
|
create_stream();
|
|
}
|
|
});
|
|
|
|
container.on("click", ".close-invites-warning-modal", function () {
|
|
$("#invites-warning-overlay").remove();
|
|
});
|
|
|
|
container.on("click", ".confirm-invites-warning-modal", function () {
|
|
create_stream();
|
|
$("#invites-warning-overlay").remove();
|
|
});
|
|
|
|
container.on("input", "#create_stream_name", function () {
|
|
const stream_name = $.trim($("#create_stream_name").val());
|
|
|
|
// This is an inexpensive check.
|
|
stream_name_error.pre_validate(stream_name);
|
|
});
|
|
|
|
container.on("mouseover", "#announce-stream-docs", function (e) {
|
|
const announce_stream_docs = $("#announce-stream-docs");
|
|
announce_stream_docs.popover({
|
|
placement: "right",
|
|
content: render_announce_stream_docs({
|
|
notifications_stream: page_params.notifications_stream}),
|
|
html: true,
|
|
trigger: "manual"});
|
|
announce_stream_docs.popover('show');
|
|
announce_stream_docs.data('popover').tip().css('z-index', 2000);
|
|
announce_stream_docs.data('popover').tip().find('.popover-content').css('margin', '9px 14px');
|
|
e.stopPropagation();
|
|
});
|
|
container.on("mouseout", "#announce-stream-docs", function (e) {
|
|
$("#announce-stream-docs").popover('hide');
|
|
e.stopPropagation();
|
|
});
|
|
|
|
// Do not allow the user to enter newline characters while typing out the
|
|
// stream's description during it's creation.
|
|
container.on("keydown", "#create_stream_description", function (e) {
|
|
if ((e.keyCode || e.which) === 13) {
|
|
e.preventDefault();
|
|
}
|
|
});
|
|
|
|
};
|
|
|
|
window.stream_create = exports;
|