redesign: Convert subscriptions page to overlay.

This is a major change to the /#subscriptions page, converting it to
by a side-by-side list of streams and their settings in an overlay.
There are no new features added/removed, but it's a huge changeset,
because it replaces the old navigation logic and moves the stream
creation modal to appear in the right side of this overlay.
This commit is contained in:
Brock Whittaker
2016-11-01 14:32:10 -07:00
committed by Tim Abbott
parent 67f28fe62d
commit 1886f0a015
24 changed files with 593 additions and 245 deletions

View File

@@ -14,29 +14,27 @@ casper.then(function () {
casper.test.assertUrlMatch(
/^http:\/\/[^/]+\/#subscriptions/,
'URL suggests we are on subscriptions page');
casper.test.assertExists('#subscriptions.tab-pane.active', 'Subscriptions page is active');
casper.waitUntilVisible('#subscription_overlay.new-style', function () {
casper.test.assertExists('#subscription_overlay.new-style', 'Subscriptions page is active');
});
});
});
});
casper.waitForSelector('.sub_unsub_button.checked', function () {
casper.test.assertExists('.sub_unsub_button.checked', 'Initial subscriptions loaded');
casper.click('form#add_new_subscription input.btn');
});
casper.waitForSelector('#create_stream_button', function () {
casper.test.assertTextExists('Create stream', 'Modal for specifying new stream users');
casper.fill('form#stream_creation_form', {stream_name: 'Waseemio', stream_description: 'Oimeesaw'});
casper.click('form#stream_creation_form button.btn.btn-primary');
casper.click('#create_stream_button');
});
casper.then(function () {
casper.test.assertExists('#user-checkboxes [data-name="cordelia@zulip.com"]', 'Original user list contains Cordelia');
casper.test.assertExists('#user-checkboxes [data-name="hamlet@zulip.com"]', 'Original user list contains King Hamlet');
});
casper.then(function () {
casper.waitForSelector("form#stream_creation_form", function () {
casper.test.info("Filtering user list with keyword 'cor'");
casper.fill('form#stream_creation_form', {user_list_filter: 'cor'});
});
casper.then(function () {
casper.waitForSelector(".subscriber-list", function () {
casper.test.assertEquals(casper.visible('#user-checkboxes [data-name="cordelia@zulip.com"]'),
true,
"Cordelia is visible"
@@ -60,6 +58,12 @@ casper.then(function () {
"King Hamlet is visible again"
);
});
casper.waitForSelector('#stream_creation_form', function () {
casper.test.assertTextExists('Add New Stream', 'New stream creation panel');
casper.fill('form#stream_creation_form', {stream_name: 'Waseemio', stream_description: 'Oimeesaw'});
casper.click('form#stream_creation_form button.btn.btn-primary');
});
casper.waitFor(function () {
return casper.evaluate(function () {
return $('.stream-name').is(':contains("Waseemio")');
@@ -70,12 +74,10 @@ casper.then(function () {
casper.test.assertSelectorHasText('.stream-name', 'Waseemio');
casper.test.assertSelectorHasText('.description', 'Oimeesaw');
casper.fill('form#add_new_subscription', {stream_name: 'WASeemio'});
casper.click('form#add_new_subscription input.btn');
casper.click('#create_stream_button');
});
casper.waitForText('Already subscribed', function () {
casper.test.assertTextExists('Already subscribed', "Can't subscribe twice to a stream");
casper.fill('form#add_new_subscription', {stream_name: ' '});
casper.click('form#add_new_subscription input.btn');
casper.then(function () {
casper.click('#create_stream_button');
casper.fill('form#stream_creation_form', {stream_name: ' '});
casper.click('form#stream_creation_form button.btn.btn-primary');
});
@@ -83,7 +85,7 @@ casper.waitForText('A stream needs to have a name', function () {
casper.test.assertTextExists('A stream needs to have a name', "Can't create a stream with an empty name");
casper.click('form#stream_creation_form button.btn.btn-default');
casper.fill('form#add_new_subscription', {stream_name: ' '});
casper.click('form#add_new_subscription input.btn');
casper.click('#create_stream_button');
casper.fill('form#stream_creation_form', {stream_name: 'Waseemio'});
casper.click('form#stream_creation_form button.btn.btn-primary');
});
@@ -92,7 +94,7 @@ casper.waitForText('A stream with this name already exists', function () {
casper.test.info('Streams should be filtered when typing in the create box');
casper.click('form#stream_creation_form button.btn.btn-default');
});
casper.waitForText('Filter by stream name', function () {
casper.waitForText('Filter Streams', function () {
casper.test.assertSelectorHasText('.stream-row[data-stream-name="Verona"] .stream-name', 'Verona', 'Verona stream exists before filtering');
casper.test.assertSelectorDoesntHaveText('.stream-row.notdisplayed .stream-name', 'Verona', 'Verona stream shown before filtering');
});

View File

@@ -43,9 +43,9 @@ function then_navigate_to_subscriptions() {
var menu_selector = '#settings-dropdown';
casper.waitUntilVisible(menu_selector, function () {
casper.click(menu_selector);
casper.then(function () {
casper.click('a[href^="#subscriptions"]');
wait_for_tab('subscriptions');
casper.click('a[href^="#subscriptions"]');
casper.waitForSelector(".subscriptions", function () {
casper.test.assertExists('#subscriptions_table', "#subscriptions page is active");
});
});
});

View File

@@ -101,7 +101,7 @@ casper.then(function () {
casper.then(function () {
// Leave the page and return
casper.click('#settings-dropdown');
casper.click('a[href^="#subscriptions"]');
casper.click('a[href^="#"]');
casper.click('#settings-dropdown');
casper.click('a[href^="#administration"]');

View File

@@ -361,8 +361,8 @@ var people = global.people;
stream_data.add_sub(blue.name, blue);
var sub_rows = stream_data.get_streams_for_settings_page();
assert.equal(sub_rows[0].color, 'amber');
assert.equal(sub_rows[1].color, 'cinnamon');
assert.equal(sub_rows[2].color, 'blue');
assert.equal(sub_rows[0].color, 'blue');
assert.equal(sub_rows[1].color, 'amber');
assert.equal(sub_rows[2].color, 'cinnamon');
}());

Binary file not shown.

After

Width:  |  Height:  |  Size: 808 B

View File

@@ -11,6 +11,8 @@ $(function () {
var clicking = false;
var mouse_moved = false;
var meta = {};
function mousedown() {
mouse_moved = false;
clicking = true;
@@ -127,6 +129,10 @@ $(function () {
popovers.hide_all();
});
$(window).on("focus", function (e) {
meta.focusing = true;
});
// RECIPIENT BARS
function get_row_id_for_narrowing(narrow_link_elem) {
@@ -212,7 +218,19 @@ $(function () {
popovers.hide_all();
});
$("#subscriptions_table").on("click", ".exit, #subscription_overlay", function (e) {
if (meta.focusing) {
meta.focusing = false;
return;
}
if ($(e.target).is(".exit, .exit-sign, #subscription_overlay, #subscription_overlay > .flex")) {
$("#subscription_overlay").fadeOut(500);
subs.remove_miscategorized_streams();
hashchange.exit_settings();
}
});
// HOME
// Capture both the left-sidebar Home click and the tab breadcrumb Home
@@ -238,12 +256,6 @@ $(function () {
// MISC
$('#streams_inline_cog').click(function (e) {
ui.change_tab_to('#subscriptions');
e.preventDefault();
});
(function () {
var sel = ["#group-pm-list", "#stream_filters", "#global_filters", "#user_presences"].join(", ");

View File

@@ -46,19 +46,6 @@ exports.initialize = function () {
}
});
var subs_link = $('#gear-menu a[href="#subscriptions"]');
// If the streams page is shown by clicking directly on the "Streams"
// link (in the gear menu), then focus the new stream textbox.
subs_link.on('click', function (e) {
$(document).one('subs_page_loaded.zulip', function (e) {
$('#create_or_filter_stream_row input[type="text"]').focus().select();
});
});
// Whenever the streams page comes up (from anywhere), populate it.
subs_link.on('shown', subs.setup_page);
// The admin and settings pages are generated client-side through
// templates.

View File

@@ -196,12 +196,16 @@ function get_main_hash(hash) {
function should_ignore(hash) {
// an array of hashes to ignore (eg. ["subscriptions", "settings", "administration"]).
var ignore_list = [];
var ignore_list = ["subscriptions"];
var main_hash = get_main_hash(hash);
return (ignore_list.indexOf(main_hash) > -1);
}
function hide_overlays() {
$("#subscription_overlay").fadeOut(500);
}
function hashchanged(from_reload, e) {
var old_hash;
if (e) {
@@ -212,9 +216,14 @@ function hashchanged(from_reload, e) {
var base = get_main_hash(window.location.hash);
if (should_ignore(window.location.hash)) {
if (!should_ignore(old_hash || "#")) {
if (base === "subscriptions") {
subs.launch();
}
ignore.prev = old_hash;
}
} else if (!should_ignore(window.location.hash) && !ignore.flag) {
hide_overlays();
changing_hash = true;
var ret = do_hashchange(from_reload);
changing_hash = false;

View File

@@ -190,9 +190,14 @@ function process_hotkey(e) {
}
}
if (event_name === "escape" && $("#overlay").hasClass("show")) {
ui.exit_lightbox_photo();
return true;
if (event_name === "escape") {
if ($("#overlay").hasClass("show")) {
ui.exit_lightbox_photo();
return true;
} else if ($("#subscription_overlay").css("display") === "block") {
$("#subscription_overlay").click();
return true;
}
}
// Process hotkeys specially when in an input, select, textarea, or send button

View File

@@ -685,17 +685,13 @@ exports.register_click_handlers = function () {
$('body').on('click', '.open_stream_settings', function (e) {
var stream = $(e.currentTarget).parents('ul').attr('data-name');
popovers.hide_stream_sidebar_popover();
if (! $('#subscriptions').hasClass('active')) {
// Go to streams page and once it loads, expand the relevant
// stream's settings.
$(document).one('subs_page_loaded.zulip', function (event) {
subs.show_settings_for(stream);
});
ui.change_tab_to('#subscriptions');
} else {
// Already on streams page, so just expand the relevant stream.
subs.show_settings_for(stream);
}
window.location.hash = "#subscriptions";
// the template for subs needs to render.
subs.onlaunch("narrow_to_row", function () {
$(".stream-row[data-stream-name='" + stream + "']").click();
}, true);
});
};

View File

@@ -329,7 +329,7 @@ exports.get_streams_for_settings_page = function () {
}
subscribed_rows.sort(by_name);
unsubscribed_rows.sort(by_name);
var all_subs = subscribed_rows.concat(unsubscribed_rows);
var all_subs = unsubscribed_rows.concat(subscribed_rows);
// Add in admin options and stream counts.
var sub_rows = [];

View File

@@ -1,5 +1,9 @@
var subs = (function () {
var meta = {
callbacks: {},
stream_created: false
};
var exports = {};
function settings_for_sub(sub) {
@@ -240,7 +244,7 @@ exports.set_color = function (stream_id, color) {
exports.rerender_subscribers_count = function (sub) {
var id = parseInt(sub.stream_id, 10);
stream_data.update_subscribers_count(sub);
$(".stream-row[data-stream-id='" + id + "'] .subscriber-count").text(sub.subscriber_count);
$(".stream-row[data-stream-id='" + id + "'] .subscriber-count-text").text(sub.subscriber_count);
};
function add_email_hint(row, email_address_hint_content) {
@@ -266,10 +270,17 @@ function add_sub_to_table(sub) {
sub = stream_data.add_admin_options(sub);
stream_data.update_subscribers_count(sub);
var html = templates.render('subscription', sub);
$('#create_or_filter_stream_row').after(html);
settings_for_sub(sub).collapse('show');
var settings_html = templates.render('subscription_settings', sub);
$(".streams-list").append(html);
$(".subscriptions .settings").append($(settings_html));
var email_address_hint_content = templates.render('email_address_hint', { page_params: page_params });
add_email_hint(sub, email_address_hint_content);
if (meta.stream_created) {
$(".stream-row[data-stream-name='" + meta.stream_created + "']").click();
meta.stream_created = false;
}
}
function format_member_list_elem(email) {
@@ -374,7 +385,6 @@ exports.show_settings_for = function (stream_name) {
var stream = $(".subscription_settings[data-stream-name='" + stream_name + "']");
$(".subscription_settings[data-stream].show").removeClass("show");
$("#subscription_overlay").fadeIn(300);
$("#subscription_overlay .subscription_settings.show").removeClass("show");
sub_settings.addClass("show");
@@ -399,11 +409,11 @@ exports.mark_subscribed = function (stream_name, attrs) {
}
var settings = settings_for_sub(sub);
var button = button_for_sub(sub);
if (button.length !== 0) {
exports.rerender_subscribers_count(sub);
button.toggleClass("checked");
button.parent().children(".preview-stream").text(i18n.t("Narrow"));
// Add the user to the member list if they're currently
// viewing the members of this stream
if (sub.render_subscribers && settings.hasClass('in')) {
@@ -448,7 +458,6 @@ exports.mark_sub_unsubscribed = function (sub) {
var button = button_for_sub(sub);
button.toggleClass("checked");
button.parent().children(".preview-stream").text(i18n.t("Preview"));
var settings = settings_for_sub(sub);
if (settings.hasClass('in')) {
@@ -477,8 +486,19 @@ exports.mark_sub_unsubscribed = function (sub) {
}
$(document).trigger($.Event('subscription_remove_done.zulip', {sub: sub}));
$(".stream-row[data-stream-id='" + sub.stream_id + "']").attr("data-temp-view", true);
};
// these streams are miscategorized so they don't jump off the page when being
// unsubscribed from, but should be cleared and sorted when you apply an actual
// filter.
function remove_temporarily_miscategorized_streams() {
$("[data-temp-view]").removeAttr("data-temp-view", "false");
}
exports.remove_miscategorized_streams = remove_temporarily_miscategorized_streams;
// query is now an object rather than a string.
// Query { input: String, subscribed_only: Boolean }
exports.filter_table = function (query) {
@@ -494,9 +514,11 @@ exports.filter_table = function (query) {
var sub_name = sub.name.toLowerCase();
var matches_list = search_terms.indexOf(sub_name) > -1;
var matches_last_val = sub_name.match(search_terms[search_terms.length - 1]);
return matches_list || matches_last_val;
}());
flag = flag && (sub.subscribed || !query.subscribed_only);
flag = flag && ((sub.subscribed || !query.subscribed_only) ||
$(row).attr("data-temp-view") === "true");
if (flag) {
$(row).removeClass("notdisplayed");
@@ -504,17 +526,50 @@ exports.filter_table = function (query) {
$(row).addClass("notdisplayed");
}
});
if ($(".stream-row.active").hasClass("notdisplayed")) {
$(".right .settings").hide();
$(".nothing-selected").show();
$(".stream-row.active").removeClass("active");
}
};
function actually_filter_streams() {
var search_box = $("#create_or_filter_stream_row input[type='text']");
var search_box = $("#add_new_subscription input[type='text']");
var query = search_box.expectOne().val().trim();
exports.filter_table({input: query});
var subscribed_only;
if (components.toggle.lookup("stream-filter-toggle")) {
subscribed_only = components.toggle.lookup("stream-filter-toggle").value() === "Subscribed";
} else {
subscribed_only = false;
}
exports.filter_table({ input: query, subscribed_only: subscribed_only });
}
var filter_streams = _.throttle(actually_filter_streams, 50);
exports.setup_page = function () {
exports.setup_page = function (callback) {
function initialize_components() {
var stream_filter_toggle = components.toggle({
name: "stream-filter-toggle",
selected: 0,
values: [
{ label: "Subscribed" },
{ label: "All Streams" },
],
callback: function (name) {
actually_filter_streams();
remove_temporarily_miscategorized_streams();
}
}).get();
if (should_list_all_streams()) {
$("#subscriptions_table .search-container").prepend(stream_filter_toggle);
}
// show the "Stream Settings" header by default.
$(".display-type #stream_settings_title").show();
}
function _populate_and_fill() {
var sub_rows = stream_data.get_streams_for_settings_page();
@@ -528,14 +583,24 @@ exports.setup_page = function () {
};
var rendered = templates.render('subscription_table_body', template_data);
$('#subscriptions_table').append(rendered);
initialize_components();
actually_filter_streams();
var email_address_hint_content = templates.render('email_address_hint', { page_params: page_params });
_.each(sub_rows, function (row) {
add_email_hint(row, email_address_hint_content);
});
$("#create_or_filter_stream_row input[type='text']").on("input", filter_streams);
$("#add_new_subscription input[type='text']").on("input", function () {
remove_temporarily_miscategorized_streams();
actually_filter_streams();
});
$(document).trigger($.Event('subs_page_loaded.zulip'));
if (callback) {
callback();
exports.onlaunchtrigger();
}
}
function populate_and_fill() {
@@ -551,6 +616,34 @@ exports.setup_page = function () {
}
};
// add a function to run on subscription page launch by name,
// and specify whether it should be kept or just run once (boolean).
exports.onlaunch = function (name, callback, keep) {
meta.callbacks[name] = {
func: callback,
keep: keep
};
};
exports.onlaunchtrigger = function () {
for (var x in meta.callbacks) {
if (typeof meta.callbacks[x].func === "function") {
meta.callbacks[x].func();
// delete if it should not be kept.
if (!meta.callbacks[x].keep) {
delete meta.callbacks[x];
}
}
}
};
exports.launch = function () {
exports.setup_page(function () {
$("#subscription_overlay").fadeIn(300);
});
};
exports.update_subscription_properties = function (stream_name, property, value) {
var sub = stream_data.get_sub(stream_name);
if (sub === undefined) {
@@ -601,7 +694,8 @@ function ajaxSubscribe(stream) {
data: {subscriptions: JSON.stringify([{name: stream}]) },
success: function (resp, statusText, xhr, form) {
$("#create_stream_name").val("");
exports.filter_table({input: ""});
actually_filter_streams();
var res = JSON.parse(xhr.responseText);
if (!$.isEmptyObject(res.already_subscribed)) {
@@ -636,10 +730,6 @@ function ajaxUnsubscribe(stream) {
});
}
function hide_new_stream_modal() {
$('#stream-creation').modal("hide");
}
function ajaxSubscribeForCreation(stream, description, principals, invite_only, announce) {
// Subscribe yourself and possible other people to a new stream.
return channel.post({
@@ -653,13 +743,11 @@ function ajaxSubscribeForCreation(stream, description, principals, invite_only,
$("#create_stream_name").val("");
$("#create_stream_description").val("");
$("#subscriptions-status").hide();
hide_new_stream_modal();
// The rest of the work is done via the subscribe event we will get
},
error: function (xhr) {
ui.report_error(i18n.t("Error creating stream"), xhr,
$("#subscriptions-status"), 'subscriptions-status');
hide_new_stream_modal();
}
});
}
@@ -684,6 +772,8 @@ function update_announce_stream_state() {
}
function show_new_stream_modal() {
$("#stream-creation").removeClass("hide");
$(".right .settings").hide();
$('#people_to_add').html(templates.render('new_stream_users', {
users: people.get_rest_of_realm()
}));
@@ -695,8 +785,6 @@ function show_new_stream_modal() {
$('#announce-new-stream input').prop('checked', true);
$("#stream_name_error").hide();
$('#stream-creation').modal("show");
}
exports.invite_user_to_stream = function (user_email, stream_name, success, failure) {
@@ -729,8 +817,31 @@ $(function () {
// when new messages come in, but it's fairly quick.
stream_list.build_stream_list();
$("#subscriptions_table").on("submit", "#add_new_subscription", function (e) {
var show_subs_pane = {
nothing_selected: function () {
$(".nothing-selected, #stream_settings_title").show();
$("#add_new_stream_title, .settings, #stream-creation").hide();
},
stream_creation: function () {
$("#stream-creation, #add_new_stream_title").show();
$("#stream_settings_title, .settings, .nothing-selected").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();
// 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");
show_subs_pane.stream_creation();
if (!should_list_all_streams()) {
ajaxSubscribe($("#search_stream_name").val());
@@ -738,19 +849,18 @@ $(function () {
}
var stream = $.trim($("#search_stream_name").val());
var stream_status = compose.check_stream_existence(stream);
if (stream_status === "does-not-exist" || !stream) {
$('#create_stream_name').val(stream);
show_new_stream_modal();
$('#create_stream_name').focus();
} else {
ajaxSubscribe(stream);
}
$('#create_stream_name').val(stream);
show_new_stream_modal();
$('#create_stream_name').focus();
});
$('#stream_creation_form').on('change',
'#user-checkboxes input, #make-invite-only input',
update_announce_stream_state);
$('body').on('change', '#user-checkboxes input, #make-invite-only input', update_announce_stream_state);
$(".subscriptions").on("click", "[data-dismiss]", function (e) {
e.preventDefault();
show_subs_pane.nothing_selected();
});
// 'Check all' and 'Uncheck all' links
$(document).on('click', '.subs_set_all_users', function (e) {
@@ -786,35 +896,38 @@ $(function () {
e.preventDefault();
});
var announce_stream_docs = $("#announce-stream-docs");
announce_stream_docs.popover({placement: "right",
content: templates.render('announce_stream_docs'),
trigger: "manual"});
$("body").on("mouseover", "#announce-stream-docs", function (e) {
var announce_stream_docs = $("#announce-stream-docs");
announce_stream_docs.popover({placement: "right",
content: templates.render('announce_stream_docs'),
trigger: "manual"});
announce_stream_docs.popover('show');
announce_stream_docs.data('popover').tip().css('z-index', 2000);
e.stopPropagation();
});
$("body").on("mouseout", "#announce-stream-docs", function (e) {
announce_stream_docs.popover('hide');
$("#announce-stream-docs").popover('hide');
e.stopPropagation();
});
$("#create_stream_name").on("focusout", function () {
$(".subscriptions").on("focusout", "#create_stream_name", function () {
var stream = $.trim($("#create_stream_name").val());
var stream_status = compose.check_stream_existence(stream);
if (stream.length < 1) {
if (stream.length !== 0) {
var stream_status = compose.check_stream_existence(stream);
if (stream_status !== "does-not-exist") {
$("#stream_name_error").text(i18n.t("A stream with this name already exists"));
$("#stream_name_error").show();
} else {
$("#stream_name_error").hide();
}
} else {
$("#stream_name_error").text(i18n.t("A stream needs to have a name"));
$("#stream_name_error").show();
} else if (stream_status !== "does-not-exist") {
$("#stream_name_error").text(i18n.t("A stream with this name already exists"));
$("#stream_name_error").show();
} else {
$("#stream_name_error").hide();
}
});
$("#stream_creation_form").on("submit", function (e) {
$(".subscriptions").on("submit", "#stream_creation_form", function (e) {
e.preventDefault();
var stream = $.trim($("#create_stream_name").val());
var description = $.trim($("#create_stream_description").val());
@@ -827,6 +940,9 @@ $(function () {
);
// You are always subscribed to streams you create.
principals.push(page_params.email);
meta.stream_created = stream;
ajaxSubscribeForCreation(stream,
description,
principals,
@@ -842,7 +958,7 @@ $(function () {
$(e.target).removeClass("btn-danger").text(i18n.t("Subscribed"));
});
$("#subscriptions-status").on("click", "#close-subscriptions-status", function (e) {
$(".subscriptions").on("click", "#close-subscriptions-status", function (e) {
$("#subscriptions-status").hide();
});
@@ -964,12 +1080,36 @@ $(function () {
exports.invite_user_to_stream(principal, stream, invite_success, invite_failure);
});
function show_stream_row(node, e) {
$(".display-type #add_new_stream_title").hide();
$(".display-type #stream_settings_title, .right .settings").show();
$(".stream-row.active").removeClass("active");
if (e) {
show_subs_pane.settings();
$(node).addClass("active");
exports.show_settings_for(get_stream_name(node));
} else {
show_subs_pane.nothing_selected();
}
}
$("#subscriptions_table").on("click", ".stream-row", function (e) {
if ($(e.target).closest(".check, .subscription_settings").length === 0) {
exports.show_settings_for(get_stream_name($(e.target)));
show_stream_row(this, e);
}
});
(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);
}
});
}());
$("#subscriptions_table").on("submit", ".subscriber_list_remove form", function (e) {
e.preventDefault();
@@ -1062,12 +1202,6 @@ $(function () {
});
});
$("body").on("click", "#subscription_overlay", function (e) {
if ($(e.target).is(".flex, #subscription_overlay")) {
$("#subscription_overlay").fadeOut(300);
}
});
function redraw_privacy_related_stuff(sub_row, sub) {
var stream_settings = settings_for_sub(sub);
var html;
@@ -1080,6 +1214,16 @@ $(function () {
html = templates.render('subscription_type', sub);
stream_settings.find('.subscription-type').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("");
}
html = templates.render('change_stream_privacy', sub);
stream_settings.find('.change-stream-privacy').expectOne().html(html);

View File

@@ -32,6 +32,10 @@
background-color: #FFF;
}
.clear-float {
clear: both;
}
/* -- base button styling -- */
.new-style .button {
padding: 8px 15px;
@@ -215,6 +219,10 @@
-webkit-filter: grayscale(1);
}
.new-style label.checkbox input[type=checkbox]:disabled ~ span {
opacity: 0.3;
}
.new-style a.no-style {
color: inherit;
}

View File

@@ -16,12 +16,6 @@
background-color: inherit;
}
.subscription_settings,
.subscription_header,
.subscription_header.active {
background-color: #fefefe;
}
.color_swatch {
display: inline-block;
height: 18px;
@@ -234,16 +228,34 @@
}
form#add_new_subscription {
display: inline;
float: right;
margin: 0;
}
#create_stream_button {
min-width: 140px;
float: right;
margin-top: 9px;
/* margin-right: 38px will align with .sub_unsub_button
10px will align with the right edge of #subscriptions_table */
margin-right: 38px;
margin: 0px 0px 0px 5px;
border: none;
outline: none;
background-color: transparent;
font-size: 2.2em;
font-weight: 300;
line-height: 1;
color: #aaa;
display: inline-block;
transition: all 0.2s ease;
padding-bottom: 0px;
padding-top: 0px;
}
#create_stream_button:hover {
color: #444;
}
#create_stream_description {
width: calc(100% - 15px);
}
#stream_name_error {
@@ -357,12 +369,14 @@ form#add_new_subscription {
.subscriptions-container {
position: relative;
height: 80%;
height: 95%;
background-color: #fff;
border-radius: 4px;
padding: 0px;
width: 900px;
width: 97%;
overflow: hidden;
max-width: 1200px;
max-height: 1000px;
}
.subscriptions-header {
@@ -376,7 +390,7 @@ form#add_new_subscription {
.subscriptions-container .exit {
font-weight: 400;
position: absolute;
top: 5px;
top: 10px;
right: 10px;
color: #AAA;
cursor: pointer;
@@ -384,19 +398,31 @@ form#add_new_subscription {
.subscriptions-container .exit-sign {
position: relative;
top: 2px;
top: 3px;
margin-left: 3px;
font-size: 1.7em;
font-weight: 300;
cursor: pointer;
}
.subscriptions-container .left,
.subscriptions-container .right {
vertical-align: top;
position: relative;
display: inline-block;
vertical-align: top;
width: 50%;
height: calc(100% - 39px);
margin: 0px -2px;
height: calc(100% - 45px);
margin: 0px -1px;
}
.subscriptions-container .right .nothing-selected {
display: block;
margin-top: calc(45vh - 30px - 1em);
text-align: center;
font-size: 1em;
color: #aaa;
pointer-events: none;
-webkit-font-smoothing: antialiased;
}
.subscriptions-container .left {
@@ -423,10 +449,26 @@ form#add_new_subscription {
content: "Preferences";
}
.subscriptions-container .right iframe {
.subscriptions-container .display-type .stream-info-title {
display: none;
font-size: 1em;
line-height: 1;
margin: 9px 0px;
font-weight: 400;
font-weight: 600;
color: #444;
}
.subscriptions-container .right #preview_iframe {
width: 100%;
height: calc(100% - 41px);
height: calc(100% - 45px);
border: none;
background-color: #FAFAFA;
box-shadow: inset 0px 0px 50px rgba(0,0,0,0.5);
background-image: url(../images/preview-loading.png);
background-size: 150px auto;
background-position: center center;
background-repeat: no-repeat;
}
.subscriptions-container input[type=text].small {
@@ -446,28 +488,54 @@ form#add_new_subscription {
text-align: left;
}
#find_streams {
width: 194px;
#search_stream_name {
width: 190px;
padding: 3px 5px;
margin: 2px 0px;
display: inline-block;
border-radius: 5px;
box-shadow: none;
}
.create-stream-title {
font-size: 1.5em;
font-weight: 600;
}
.stream-creation-body section.block {
margin-bottom: 20px;
}
.stream-creation-body #make-invite-only label span.icon-vector-globe {
margin-left: 14px;
margin-right: 10px;
}
.stream-creation-body #make-invite-only label span.icon-vector-lock {
margin-left: 15px;
margin-right: 11px;
}
.stream-creation-body #announce-new-stream div[class^="icon"] {
margin-left: 3px;
margin-right: 8px;
}
.streams-list {
overflow: auto;
height: calc(100% - 41px);
height: calc(100% - 45px);
width: 100%;
}
.stream-row {
padding: 15px 10px;
border-bottom: 1px solid #ddd;
border-bottom: 1px solid #eee;
cursor: pointer;
}
.stream-row.active {
background-color: #eee;
border-bottom: 1px solid #eee;
}
.stream-row.no-border {
border-bottom: 1px solid #eee;
}
.stream-row > div {
@@ -534,11 +602,16 @@ form#add_new_subscription {
margin-left: 10px;
}
.stream-row .sub-info-box .top-bar .subscriber-count-text {
margin-right: 5px;
}
.stream-row .sub-info-box .description {
margin-top: 5px;
margin-top: 2px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1;
}
#subscription_overlay .stream-description:empty:after,
@@ -548,12 +621,36 @@ form#add_new_subscription {
color: #aaa;
}
#subscription_overlay #stream-creation {
max-height: calc(100% - 102px);
overflow: auto;
outline: none;
}
#subscription_overlay #stream-creation .modal-footer {
position: absolute;
bottom: 0;
width: calc(100% - 30px);
}
#stream-creation .stream-creation-body {
padding: 15px;
}
.stream-row .settings-dropdown-trigger {
float: right;
margin-left: 10px;
color: #aaa;
}
.add-user-label {
margin: 8px 0px;
}
#stream_creation_form {
margin: 0px;
}
#subscription_overlay .inner-box {
margin: 20px;
}
@@ -613,15 +710,19 @@ form#add_new_subscription {
display: block;
}
#subscription_overlay .settings {
position: relative;
height: calc(100% - 45px);
overflow-y: auto;
}
#subscription_overlay .subscription_settings {
display: none;
position: relative;
width: 600px;
width: 100%;
margin: 0 auto;
border-radius: 4px;
top: -1px;
max-height: 85vh;
overflow: auto;
}
#subscription_overlay .subscription_settings.show {
@@ -652,3 +753,71 @@ form#add_new_subscription {
#subscription_overlay .subscription_settings ul li .sp-replacer {
box-shadow: none;
}
@media (max-width: 1130px) {
.subscriptions-container {
max-width: 95%;
}
#subscription_overlay .left {
width: 40%;
}
#subscription_overlay .right {
width: 60%;
}
#search_stream_name {
width: 100px;
margin-top: 2px;
margin-bottom: 0px;
}
}
@media (max-width: 1024px) {
#search_stream_name {
display: none;
}
}
@media (max-width: 1000px) {
.search-container {
text-align: center;
}
}
@media (max-width: 700px) {
#subscription_overlay .left,
#subscription_overlay .right {
display: block;
width: 100%;
}
#subscription_overlay .left {
overflow: auto;
height: 30%;
}
#subscription_overlay .right {
overflow: auto;
height: calc(70% - 45px);
border-top: 1px solid #ddd;
}
#subscription_overlay .display-type {
display: none;
}
#subscription_overlay .subscriptions-container {
height: 95%;
}
#subscription_overlay .subscription_settings {
overflow: visible;
}
#subscription_overlay .settings {
height: auto;
overflow: visible;
}
}

View File

@@ -4,8 +4,10 @@
<input class="add-user-list-filter" name="user_list_filter" type="text" placeholder="{{t "Filter users" }}" />
<div id="user-checkboxes">
{{#each users}}
<label class="checkbox" data-name="{{this.email}}">
<input type="checkbox" name="user" value="{{this.email}}" /> {{this.full_name}} ({{this.email}})
<label class="checkbox add-user-label" data-name="{{this.email}}">
<input type="checkbox" name="user" value="{{this.email}}" />
<span></span>
{{this.full_name}} ({{this.email}})
</label>
{{/each}}
</div>

View File

@@ -10,12 +10,9 @@
<div class="sub-info-box">
<div class="top-bar">
<div class="stream-name">{{name}}</div>
<div class="settings-dropdown-trigger">
<i class="icon-vector-chevron-down"></i>
</div>
<div class="subscriber-count">
<span class="subscriber-count-text">{{subscriber_count}}</span>
<i class="icon-vector-user"></i>
{{subscriber_count}}
</div>
</div>
<div class="description" data-no-description="{{t 'No description.'}}">{{description}}</div>

View File

@@ -0,0 +1,54 @@
<div class="hide" id="stream-creation" tabindex="-1" role="dialog"
aria-labelledby="stream-creation-label" aria-hidden="true">
<form id="stream_creation_form" class="form-inline">
<div class="stream-creation-body">
<section class="block">
<div class="create-stream-title">
{{t "Stream name" }}
</div>
<input type="text" name="stream_name" id="create_stream_name"
placeholder="{{t 'Stream name' }}" value="" autocomplete="off" />
<div id="stream_name_error"></div>
</section>
<section class="block">
<b>{{t "Stream description (optional)"}}</b><br />
<textarea name="stream_description" id="create_stream_description"
placeholder="{{t 'Stream description' }}" value="" autocomplete="off"></textarea>
</section>
<section class="block" id="make-invite-only">
<div class="create-stream-title">
{{t "Stream accessibility" }}
</div>
<label class="radio">
<input type="radio" name="privacy" value="public" checked />
<span class="icon-vector-globe"></span>
{{t "Anyone can join" }}
</label><br />
<label class="radio">
<input type="radio" name="privacy" value="invite-only" />
<span class="icon-vector-lock"></span>
{{t "People must be invited" }}
</label>
<div id="announce-new-stream">
<label class="checkbox">
<input type="checkbox" name="announce" value="announce" checked />
<span></span>
<div class="icon-vector-bullhorn"></div>
{{t "Announce stream" }}
</label>
<span class="icon-vector-question-sign" id="announce-stream-docs"></span>
</div>
</section>
<section class="block">
<label class="control-label" for="people_to_add">
{{t "People to add" }}
</label>
<div class="controls" id="people_to_add"></div>
</section>
</div>
<div class="modal-footer">
<button class="btn btn-default" data-dismiss="modal" aria-hidden="true">{{t "Cancel" }}</button>
<button class="btn btn-primary" type="submit">{{t "Create" }}</button>
</div>
</form>
</div>

View File

@@ -1,24 +1,46 @@
<div id="create_or_filter_stream_row">
<div class="subscription_table_elem">
<form id="add_new_subscription" class="form-inline" action="">
<input type="text" name="stream_name" id="search_stream_name"
placeholder="{{t 'Filter by stream name' }}" value="" autocomplete="off" />
{{#if can_create_streams}}
<input type="submit" class="btn btn-primary"
id="create_stream_button" value="{{t 'Create new stream' }}" />
{{/if}}
</form>
</div>
</div>
{{#each subscriptions}}
{{partial "subscription"}}
{{/each}}
<div id="subscription_overlay">
<div id="subscription_overlay" class="new-style">
<div class="flex">
{{#each subscriptions}}
{{ partial "subscription_settings" }}
{{/each}}
<div class="subscriptions-container">
<div class="subscriptions-header">
Subscriptions
<div class="exit">
<span class="exit-sign">&times;</span>
</div>
</div>
<div class="left">
<div class="search-container">
<form id="add_new_subscription" class="form-inline" action="">
<input type="text" name="stream_name" id="search_stream_name"
placeholder="{{t 'Filter Streams' }}" value="" autocomplete="off" />
{{#if can_create_streams}}
<input type="submit" class=""
id="create_stream_button" value="{{t '+' }}" />
{{/if}}
<div class="float-clear"></div>
</form>
<div class="clear-float"></div>
</div>
<div class="streams-list">
{{#each subscriptions}}
{{partial "subscription"}}
{{/each}}
</div>
</div>
<div class="right">
<div class="display-type">
<div id="add_new_stream_title" class="stream-info-title">Add New Stream</div>
<div id="stream_settings_title" class="stream-info-title">Stream Settings</div>
</div>
<div class="nothing-selected">
Nothing selected.
</div>
<div class="settings">
{{#each subscriptions}}
{{ partial "subscription_settings" }}
{{/each}}
</div>
{{ partial "subscription_creation_form" }}
</div>
</div>
</div>
</div>

View File

@@ -102,9 +102,6 @@ var page_params = {{ page_params }};
</div>
{% include "zerver/home.html" %}
</div>
<div class="tab-pane" id="subscriptions">
{% include "zerver/subscriptions.html" %}
</div>
<div class="tab-pane new-style" id="administration">
</div>
<div class="tab-pane new-style" id="settings">
@@ -121,6 +118,7 @@ var page_params = {{ page_params }};
{% include "zerver/right-sidebar.html" %}
</div><!--/right sidebar-->
{% include "zerver/image-overlay.html" %}
{% include "zerver/subscriptions.html" %}
</div><!--/row-->
{% include "zerver/keyboard_shortcuts.html" %}
{% include "zerver/search_operators.html" %}

View File

@@ -10,7 +10,9 @@
</ul>
<div id="streams_list" class="zoom-out">
<div id="streams_header" class="zoom-in-hide"><h4 class="sidebar-title" data-toggle="tooltip" title="Subscribed streams"><a href="">{{ _('STREAMS') }}</a></h4>
<a href=""><i id="streams_inline_cog" class='icon-vector-cog' data-toggle="tooltip" title="Subscribe, add, or configure streams"></i></a>
<a href="#subscriptions">
<i id="streams_inline_cog" class='icon-vector-cog' data-toggle="tooltip" title="Subscribe, add, or configure streams"></i>
</a>
<a href=""><i id='streams_filter_icon' class='icon-vector-search' data-toggle="tooltip" title="Filter streams list"></i></a>
</div>
<div id="topics_header">

View File

@@ -50,7 +50,7 @@
#}
<li style="display:none;"><a href="#home" data-toggle="tab"></a></li>
<li title="Manage Streams">
<a href="#subscriptions" data-toggle="tab">
<a href="#subscriptions">
<i class="icon-vector-exchange"></i> {{ _('Manage Streams') }}
</a>
</li>

View File

@@ -1,52 +0,0 @@
<div class="modal hide" id="stream-creation" tabindex="-1" role="dialog"
aria-labelledby="stream-creation-label" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="stream-creation-label">{% trans %}Create stream{% endtrans %}</h3>
</div>
<form id="stream_creation_form" class="form-inline">
<div class="modal-body">
<div>
<b>{% trans %}Stream name{% endtrans %}</b><br />
<input type="text" name="stream_name" id="create_stream_name"
placeholder="{{ _('Stream name') }}" value="" autocomplete="off" />
<div id="stream_name_error"></div>
</div>
<div>
<b>{% trans %}Stream description (optional){% endtrans %}</b><br />
<input type="text" name="stream_description" id="create_stream_description"
placeholder="{{_('Stream description') }}" value="" autocomplete="off" />
</div>
<div id="make-invite-only">
<b>{% trans %}Stream accessibility{% endtrans %}</b><br />
<label class="radio">
<input type="radio" name="privacy" value="public" checked />
<span class="icon-vector-globe"></span>
{% trans %}Anyone can join{% endtrans %}
</label><br />
<label class="radio">
<input type="radio" name="privacy" value="invite-only" />
<span class="icon-vector-lock"></span>
{% trans %}People must be invited{% endtrans %}
</label>
</div>
<div id="announce-new-stream">
<br />
<label class="checkbox">
<input type="checkbox" name="announce" value="announce" checked />
{% trans %}Announce stream{% endtrans %}
</label>
<span class="icon-vector-question-sign" id="announce-stream-docs"></span>
</div>
<br />
<div class="control-group">
<label class="control-label" for="people_to_add"><b>{% trans %}People to add{% endtrans %}</b></label><br />
<div class="controls" id="people_to_add"></div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default" data-dismiss="modal" aria-hidden="true">{{ _("Cancel") }}</button>
<button class="btn btn-primary" type="submit">{{ _("Create") }}</button>
</div>
</form>
</div>

View File

@@ -1,14 +1,5 @@
{# Subscriptions management tab of the app. #}
<div class="subscriptions">
<h1><i class="icon-vector-exchange streams-icon"></i>{{ _("Streams") }}</h1>
<div class="alert" id="subscriptions-status">
<span id="response"></span>
<span id="close-subscriptions-status"><i class="icon-vector-remove"></i></span>
</div>
<div id="subs_page_loading_indicator"></div>
<div id="subscriptions_table">
</div>
</div>
{% include "zerver/stream_creation_prompt.html" %}
<div class="subscriptions">
<div id="subscriptions_table">
</div>
</div>

View File

@@ -23,6 +23,8 @@ GENERIC_KEYWORDS = [
'warning',
'zoom-in', # TODO: clean these up, they are confusing
'zoom-out',
'first',
'second',
]
def raise_error(fn, i, line):