invite_user: Convert overlay to `dialog_widget`.

Fixes #22957.
This commit is contained in:
Ganesh Pawar 2022-10-11 21:32:39 +05:30 committed by Tim Abbott
parent a0d15f3029
commit 537617b46d
11 changed files with 222 additions and 287 deletions

View File

@ -8,7 +8,6 @@ import * as drafts from "./drafts";
import * as hash_util from "./hash_util";
import {$t_html} from "./i18n";
import * as info_overlay from "./info_overlay";
import * as invite from "./invite";
import * as message_lists from "./message_lists";
import * as message_viewport from "./message_viewport";
import * as narrow from "./narrow";
@ -338,11 +337,6 @@ function do_hashchange_overlay(old_hash) {
return;
}
if (base === "invite") {
invite.launch();
return;
}
if (base === "keyboard-shortcuts") {
info_overlay.show("keyboard-shortcuts");
return;

View File

@ -5,16 +5,14 @@ import $ from "jquery";
import copy_invite_link from "../templates/copy_invite_link.hbs";
import render_invitation_failed_error from "../templates/invitation_failed_error.hbs";
import render_invite_subscription from "../templates/invite_subscription.hbs";
import render_invite_user from "../templates/invite_user.hbs";
import render_invite_user_modal from "../templates/invite_user_modal.hbs";
import render_settings_dev_env_email_access from "../templates/settings/dev_env_email_access.hbs";
import * as browser_history from "./browser_history";
import * as channel from "./channel";
import * as common from "./common";
import * as dialog_widget from "./dialog_widget";
import * as gear_menu from "./gear_menu";
import {$t, $t_html} from "./i18n";
import * as keydown_util from "./keydown_util";
import * as overlays from "./overlays";
import {page_params} from "./page_params";
import * as settings_config from "./settings_config";
import * as stream_data from "./stream_data";
@ -27,8 +25,7 @@ let custom_expiration_time_input = 10;
let custom_expiration_time_unit = "days";
function reset_error_messages() {
$("#invite_status").hide().text("").removeClass(common.status_classes);
$("#multiuse_invite_status").hide().text("").removeClass(common.status_classes);
$("#dialog_error").hide().text("").removeClass(common.status_classes);
if (page_params.development_environment) {
$("#dev_env_msg").hide().text("").removeClass(common.status_classes);
@ -68,14 +65,14 @@ function beforeSend() {
// aren't in the right domain, etc.)
//
// OR, you could just let the server do it. Probably my temptation.
const loading_text = $("#submit-invitation").data("loading-text");
$("#submit-invitation").text(loading_text);
$("#submit-invitation").prop("disabled", true);
const loading_text = $("#invite-user-modal .dialog_submit_button").data("loading-text");
$("#invite-user-modal .dialog_submit_button").text(loading_text);
$("#invite-user-modal .dialog_submit_button").prop("disabled", true);
return true;
}
function submit_invitation_form() {
const $invite_status = $("#invite_status");
const $invite_status = $("#dialog_error");
const $invitee_emails = $("#invitee_emails");
const data = get_common_invitation_data();
data.invitee_emails = $("#invitee_emails").val();
@ -145,16 +142,17 @@ function submit_invitation_form() {
}
},
complete() {
$("#submit-invitation").text($t({defaultMessage: "Invite"}));
$("#submit-invitation").prop("disabled", false);
$("#invite-user-modal .dialog_submit_button").text($t({defaultMessage: "Invite"}));
$("#invite-user-modal .dialog_submit_button").prop("disabled", false);
$("#invite-user-modal .dialog_cancel_button").prop("disabled", false);
$("#invitee_emails").trigger("focus");
ui.get_scroll_element($("#invite_user_form .modal-body"))[0].scrollTop = 0;
ui.get_scroll_element($("#invite-user-modal"))[0].scrollTop = 0;
},
});
}
function generate_multiuse_invite() {
const $invite_status = $("#multiuse_invite_status");
const $invite_status = $("#dialog_error");
const data = get_common_invitation_data();
channel.post({
url: "/json/invites/multiuse",
@ -169,8 +167,12 @@ function generate_multiuse_invite() {
ui_report.error("", xhr, $invite_status);
},
complete() {
$("#submit-invitation").text($t({defaultMessage: "Generate invite link"}));
$("#submit-invitation").prop("disabled", false);
$("#invite-user-modal .dialog_submit_button").text(
$t({defaultMessage: "Generate invite link"}),
);
$("#invite-user-modal .dialog_submit_button").prop("disabled", false);
$("#invite-user-modal .dialog_cancel_button").prop("disabled", false);
ui.get_scroll_element($("#invite-user-modal"))[0].scrollTop = 0;
},
});
}
@ -181,42 +183,6 @@ export function get_invite_streams() {
return streams;
}
function update_subscription_checkboxes() {
const data = {
streams: get_invite_streams(),
notifications_stream: stream_data.get_notifications_stream(),
};
const html = render_invite_subscription(data);
$("#streams_to_add").html(html);
}
function prepare_form_to_be_shown() {
update_subscription_checkboxes();
reset_error_messages();
}
export function launch() {
$("#submit-invitation").button();
prepare_form_to_be_shown();
overlays.open_overlay({
name: "invite",
$overlay: $("#invite-user"),
on_close() {
browser_history.exit_overlay();
},
});
autosize($("#invitee_emails").trigger("focus"));
// Ctrl + Enter key to submit form
$("#invite-user").on("keydown", (e) => {
if (keydown_util.is_enter_event(e) && e.ctrlKey) {
submit_invitation_form();
}
});
}
function valid_to(expires_in) {
const time_valid = Number.parseFloat(expires_in);
if (!time_valid) {
@ -270,36 +236,56 @@ function set_custom_time_inputs_visibility() {
}
}
export function initialize() {
function open_invite_user_modal(e) {
e.stopPropagation();
e.preventDefault();
gear_menu.close();
const time_unit_choices = ["minutes", "hours", "days", "weeks"];
const rendered = render_invite_user({
const html_body = render_invite_user_modal({
is_admin: page_params.is_admin,
is_owner: page_params.is_owner,
development_environment: page_params.development_environment,
invite_as_options: settings_config.user_role_values,
expires_in_options: settings_config.expires_in_values,
time_choices: time_unit_choices,
streams: get_invite_streams(),
notifications_stream: stream_data.get_notifications_stream(),
});
$(".app").append(rendered);
function invite_user_modal_post_render() {
$("#invite-user-modal .dialog_submit_button").prop("disabled", true);
autosize($("#invitee_emails").trigger("focus"));
set_custom_time_inputs_visibility();
set_expires_on_text();
$(document).on("click", "#invite_check_all_button", () => {
$("#streams_to_add :checkbox").prop("checked", true);
});
$(document).on("click", "#invite_uncheck_all_button", () => {
$("#streams_to_add :checkbox").prop("checked", false);
});
$("#submit-invitation").on("click", () => {
const is_generate_invite_link = $("#generate_multiuse_invite_radio").prop("checked");
if (is_generate_invite_link) {
generate_multiuse_invite();
} else {
submit_invitation_form();
function toggle_invite_submit_button() {
$("#invite-user-modal .dialog_submit_button").prop(
"disabled",
($("#invitee_emails").val().trim() === "" &&
!$("#generate_multiuse_invite_radio").is(":checked")) ||
$("#streams_to_add input:checked").length === 0,
);
}
$("#invite-user-modal").on("input", "input, textarea, select", () => {
toggle_invite_submit_button();
});
$("#invite-user-modal").on("change", "#generate_multiuse_invite_radio", () => {
$("#invitee_emails").prop("disabled", false);
$("#invite-user-modal .dialog_submit_button").text($t({defaultMessage: "Invite"}));
$("#invite-user-modal .dialog_submit_button").data(
"loading-text",
$t({defaultMessage: "Inviting..."}),
);
$("#multiuse_radio_section").hide();
$("#invite-method-choice").show();
toggle_invite_submit_button();
reset_error_messages();
});
$("#generate_multiuse_invite_button").on("click", () => {
@ -307,36 +293,71 @@ export function initialize() {
$("#multiuse_radio_section").show();
$("#invite-method-choice").hide();
$("#invitee_emails").prop("disabled", true);
$("#submit-invitation").text($t({defaultMessage: "Generate invite link"}));
$("#submit-invitation").data("loading-text", $t({defaultMessage: "Generating link..."}));
$("#invite-user-modal .dialog_submit_button").text(
$t({defaultMessage: "Generate invite link"}),
);
$("#invite-user-modal .dialog_submit_button").data(
"loading-text",
$t({defaultMessage: "Generating link..."}),
);
$("#invite-user-modal .dialog_submit_button").prop("disabled", false);
reset_error_messages();
});
$("#invite-user").on("change", "#generate_multiuse_invite_radio", () => {
$("#invitee_emails").prop("disabled", false);
$("#submit-invitation").text($t({defaultMessage: "Invite"}));
$("#submit-invitation").data("loading-text", $t({defaultMessage: "Inviting..."}));
$("#multiuse_radio_section").hide();
$("#invite-method-choice").show();
reset_error_messages();
});
$("#expires_on").text(valid_to($("#expires_in").val()));
$("#expires_in").on("change", () => {
set_custom_time_inputs_visibility();
set_expires_on_text();
});
$("#expires_on").text(valid_to($("#expires_in").val()));
$("#custom-expiration-time-input").on("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
return;
}
});
$(".custom-expiration-time").on("change", () => {
custom_expiration_time_input = $("#custom-expiration-time-input").val();
custom_expiration_time_unit = $("#custom-expiration-time-unit").val();
$("#custom_expires_on").text(valid_to(get_expiration_time_in_minutes()));
});
$("#custom-expiration-time-input").on("keydown", (e) => {
if (keydown_util.is_enter_event(e)) {
e.preventDefault();
return;
$("#invite_check_all_button").on("click", () => {
$("#streams_to_add :checkbox").prop("checked", true);
toggle_invite_submit_button();
});
$("#invite_uncheck_all_button").on("click", () => {
$("#streams_to_add :checkbox").prop("checked", false);
$("#invite-user-modal .dialog_submit_button").prop(
"disabled",
!$("#generate_multiuse_invite_radio").is(":checked"),
);
});
}
function invite_users() {
const is_generate_invite_link = $("#generate_multiuse_invite_radio").prop("checked");
if (is_generate_invite_link) {
generate_multiuse_invite();
} else {
submit_invitation_form();
}
}
dialog_widget.launch({
html_heading: $t_html({defaultMessage: "Invite users to Zulip"}),
html_body,
html_submit_button: $t_html({defaultMessage: "Invite"}),
id: "invite-user-modal",
loading_spinner: true,
on_click: invite_users,
post_render: invite_user_modal_post_render,
});
}
export function initialize() {
$(document).on("click", ".invite-user-link", open_invite_user_modal);
}

View File

@ -56,11 +56,6 @@
box-shadow: inset 0 1px 0 hsla(0, 0%, 0%, 0.2);
}
#invite_user_form .modal-footer {
/* no transparency prevents overlap issues */
background-color: hsl(211, 28%, 14%);
}
.enter_sends,
.enter_sends_choices {
color: hsl(236, 33%, 90%);
@ -1233,6 +1228,14 @@
box-shadow: 0 5px 10px hsla(0, 0%, 0%, 0.4);
}
#invite-user-modal {
#clipboard_image {
path {
fill: hsl(236, 33%, 90%);
}
}
}
#user-profile-modal {
#default-section {
.default-field {

View File

@ -89,12 +89,6 @@ $user_status_emoji_width: 24px;
}
}
#invite-user-link i {
text-decoration: none;
margin-right: 3px;
margin-left: 5px;
}
.user-presence-link,
.user_sidebar_entry .selectable_sidebar_block {
display: flex;

View File

@ -2372,6 +2372,7 @@ textarea.invitee_emails {
max-height: 300px;
width: 96%;
max-width: 96%;
resize: vertical !important;
color: hsl(0, 0%, 33%);
background-color: hsl(0, 0%, 100%);
@ -2397,42 +2398,10 @@ textarea.invitee_emails {
}
}
#invite-user {
.modal-header {
padding: 7px 15px;
border-color: hsl(0, 0%, 87%);
.exit {
font-size: 1.5rem;
font-weight: 600;
background-color: transparent;
border: none;
position: absolute;
top: 6px;
right: 5px;
color: hsl(0, 0%, 67%);
}
}
.overlay-content {
position: relative;
width: 500px;
border-radius: 4px;
}
.modal-body {
margin-bottom: 58px;
position: relative;
}
.modal-footer {
position: absolute;
bottom: 0;
width: calc(100% - 30px);
}
.invite-stream-controls {
margin-top: 5px;
#invite-user-modal {
#custom-expiration-time-input,
#invite-user-form {
margin: 0;
}
}
@ -2450,26 +2419,8 @@ select.invite-as {
vertical-align: middle;
}
#invite_status {
display: none;
}
#invite-user-label {
font-size: 1em;
font-weight: 700;
text-align: center;
text-transform: uppercase;
}
#multiuse_invite_status {
display: none;
margin-top: 7px;
margin-bottom: -5px;
text-align: left;
}
#multiuse_invite_link {
width: 350px;
width: 370px;
text-overflow: ellipsis;
white-space: nowrap;
display: inline-block;
@ -2478,8 +2429,6 @@ select.invite-as {
}
#invite-stream-checkboxes {
padding-bottom: 26px;
i.zulip-icon-globe {
font-size: 80%;
}

View File

@ -157,7 +157,7 @@
<li class="divider hidden-for-spectators" role="presentation"></li>
{{#if can_invite_others_to_realm}}
<li role="presentation">
<a href="#invite" role="menuitem">
<a class="invite-user-link" role="menuitem">
<i class="fa fa-user-plus" aria-hidden="true"></i> {{t 'Invite users' }}
</a>
</li>

View File

@ -1,22 +0,0 @@
{{! Client-side Handlebars template for rendering subscriptions in the "invite user" form. }}
<div class="invite-stream-controls">
<button class="btn btn-link" type="button" id="invite_check_all_button">{{t "Check all" }}</button> |
<button class="btn btn-link" type="button" id="invite_uncheck_all_button">{{t "Uncheck all" }}</button>
</div>
<div id="invite-stream-checkboxes" class="new-style">
{{#each streams}}
<label class="checkbox display-block">
<input type="checkbox" name="stream" value="{{stream_id}}"
{{#if default_stream}}checked="checked"{{/if}} />
<span></span>
{{#if (or invite_only is_web_public)}} {{>stream_privacy}} {{name}}
{{else}}
#{{name}}
{{/if}}
{{#if (eq name ../notifications_stream)}}
<i>({{t 'Receives new stream announcements' }})</i>
{{/if}}
</label>
{{/each}}
</div>

View File

@ -1,86 +0,0 @@
<div id="invite-user" class="overlay flex new-style" tabindex="-1" role="dialog" data-overlay="invite"
aria-labelledby="invite-user-label" aria-hidden="true">
<div class="overlay-content modal-bg">
<div class="modal-header">
<button type="button" class="exit" aria-label="{{t 'Close' }}"><span aria-hidden="true">&times;</span></button>
<h3 id="invite-user-label">{{t "Invite users to Zulip" }}</h3>
</div>
<form id="invite_user_form">{{ csrf_input }}
<div class="modal-body" data-simplebar data-simplebar-auto-hide="false">
<div class="alert" id="invite_status"></div>
{{#if development_environment}}
<div class="alert" id="dev_env_msg"></div>
{{/if}}
<div class="input-group">
<label for="invitee_emails">{{t "Emails (one on each line or comma-separated)" }}</label>
<div>
<textarea rows="2" id="invitee_emails" name="invitee_emails" class="invitee_emails" placeholder="{{t 'One or more email addresses...' }}"></textarea>
{{#if is_admin}}
<div id="invite-method-choice">
{{t "or" }} <a role="button" tabindex="0" id="generate_multiuse_invite_button">{{t "Generate invite link" }}</a>
</div>
<div id="multiuse_radio_section">
<label class="checkbox display-block" for="generate_multiuse_invite_radio">
<input type="checkbox" name="generate_multiuse_invite_radio" id="generate_multiuse_invite_radio"/>
<span></span>
{{t "Generate invite link" }}
</label>
</div>
{{/if}}
</div>
</div>
<div class="input-group">
<label for="expires_in">{{t "Invitation expires after" }}</label>
<div>
<select id="expires_in" class="invite-expires-in bootstrap-focus-style">
{{#each expires_in_options}}
<option {{#if this.default }}selected{{/if}} name="expires_in" value="{{this.value}}">{{this.description}}</option>
{{/each}}
</select>
</div>
<p id="expires_on"></p>
<div id="custom-invite-expiration-time" class="dependent-settings-block">
<label for="expires_in">{{t "Custom time" }}</label>
<input type="text" autocomplete="off" name="custom-expiration-time" id="custom-expiration-time-input" class="custom-expiration-time inline-block" value="{{time_input}}" maxlength="3"/>
<select class="custom-expiration-time bootstrap-focus-style" id="custom-expiration-time-unit">
{{#each time_choices}}
<option name="custom_time_choice" class="custom_time_choice" value="{{this}}">{{this}}</option>
{{/each}}
</select>
<p id="custom_expires_on"></p>
</div>
</div>
<div class="input-group">
<label for="invite_as">{{t "User(s) join as" }}
{{> help_link_widget link="/help/roles-and-permissions" }}
</label>
<div>
<select id="invite_as" class="invite-as bootstrap-focus-style">
<option name="invite_as" value="{{ invite_as_options.guest.code }}">{{t "Guests" }}</option>
<option name="invite_as" selected="selected" value="{{ invite_as_options.member.code }}">{{t "Members" }}</option>
{{#if is_admin}}
<option name="invite_as" value="{{ invite_as_options.moderator.code }}">{{t "Moderators" }}</option>
<option name="invite_as" value="{{ invite_as_options.admin.code }}">{{t "Organization administrators" }}</option>
{{/if}}
{{#if is_owner}}
<option name="invite_as" value="{{ invite_as_options.owner.code }}">{{t "Organization owners" }}</option>
{{/if}}
</select>
</div>
</div>
<div>
<label>{{t "Streams they should join" }}</label>
<div id="streams_to_add"></div>
</div>
</div>
<div class="modal-footer">
<button class="button exit small rounded" data-dismiss="modal">{{t "Cancel" }}</button>
<button id="submit-invitation" class="button small rounded sea-green" type="button"
data-loading-text="{{t 'Inviting...' }}">
{{t "Invite" }}
</button>
<div class="alert" id="multiuse_invite_status"></div>
</div>
</form>
</div>
</div>

View File

@ -0,0 +1,82 @@
<form id="invite-user-form">{{ csrf_input }}
{{#if development_environment}}
<div class="alert" id="dev_env_msg"></div>
{{/if}}
<div class="input-group">
<label for="invitee_emails">{{t "Emails (one on each line or comma-separated)" }}</label>
<textarea rows="2" id="invitee_emails" name="invitee_emails" class="invitee_emails" placeholder="{{t 'One or more email addresses...' }}"></textarea>
{{#if is_admin}}
<div id="invite-method-choice">
{{t "or" }} <a role="button" tabindex="0" id="generate_multiuse_invite_button">{{t "Generate invite link" }}</a>
</div>
<div id="multiuse_radio_section" class="new-style">
<label class="checkbox display-block" for="generate_multiuse_invite_radio">
<input type="checkbox" name="generate_multiuse_invite_radio" id="generate_multiuse_invite_radio"/>
<span></span>
{{t "Generate invite link" }}
</label>
</div>
{{/if}}
</div>
<div class="input-group">
<label for="expires_in">{{t "Invitation expires after" }}</label>
<select id="expires_in" class="invite-expires-in bootstrap-focus-style">
{{#each expires_in_options}}
<option {{#if this.default }}selected{{/if}} name="expires_in" value="{{this.value}}">{{this.description}}</option>
{{/each}}
</select>
<p id="expires_on"></p>
<div id="custom-invite-expiration-time" class="dependent-settings-block">
<label for="expires_in">{{t "Custom time" }}</label>
<input type="text" autocomplete="off" name="custom-expiration-time" id="custom-expiration-time-input" class="custom-expiration-time inline-block" value="{{time_input}}" maxlength="3"/>
<select class="custom-expiration-time bootstrap-focus-style" id="custom-expiration-time-unit">
{{#each time_choices}}
<option name="custom_time_choice" class="custom_time_choice" value="{{this}}">{{this}}</option>
{{/each}}
</select>
<p id="custom_expires_on"></p>
</div>
</div>
<div class="input-group">
<label for="invite_as">{{t "User(s) join as" }}
{{> help_link_widget link="/help/roles-and-permissions" }}
</label>
<select id="invite_as" class="invite-as bootstrap-focus-style">
<option name="invite_as" value="{{ invite_as_options.guest.code }}">{{t "Guests" }}</option>
<option name="invite_as" selected="selected" value="{{ invite_as_options.member.code }}">{{t "Members" }}</option>
{{#if is_admin}}
<option name="invite_as" value="{{ invite_as_options.moderator.code }}">{{t "Moderators" }}</option>
<option name="invite_as" value="{{ invite_as_options.admin.code }}">{{t "Organization administrators" }}</option>
{{/if}}
{{#if is_owner}}
<option name="invite_as" value="{{ invite_as_options.owner.code }}">{{t "Organization owners" }}</option>
{{/if}}
</select>
</div>
<div>
<label>{{t "Streams they should join" }}</label>
<div id="streams_to_add">
<div class="invite-stream-controls">
<button class="btn btn-link" type="button" id="invite_check_all_button">{{t "Check all" }}</button> |
<button class="btn btn-link" type="button" id="invite_uncheck_all_button">{{t "Uncheck all" }}</button>
</div>
<div id="invite-stream-checkboxes" class="new-style">
{{#each streams}}
<label class="checkbox display-block">
<input type="checkbox" name="stream" value="{{stream_id}}"
{{#if default_stream}}checked="checked"{{/if}} />
<span></span>
{{#if (or invite_only is_web_public)}} {{>stream_privacy}} {{name}}
{{else}}
#{{name}}
{{/if}}
{{#if (eq name ../notifications_stream)}}
<i>({{t 'Receives new stream announcements' }})</i>
{{/if}}
</label>
{{/each}}
</div>
</div>
</div>
</form>

View File

@ -28,7 +28,7 @@
</div>
<div class="right-sidebar-shortcuts">
{{#if can_invite_others_to_realm}}
<a id="invite-user-link" href="#invite"><i class="fa fa-user-plus" aria-hidden="true"></i>{{t 'Invite more users' }}</a>
<a class="invite-user-link" role="button"><i class="fa fa-user-plus" aria-hidden="true"></i>{{t 'Invite more users' }}</a>
{{/if}}
<a id="sidebar-keyboard-shortcuts" data-overlay-trigger="keyboard-shortcuts" class="hidden-for-spectators">
<i class="fa fa-keyboard-o fa-2x tippy-zulip-tooltip" id="keyboard-icon" data-tooltip-template-id="keyboard-icon-tooltip-template"></i>

View File

@ -4,7 +4,7 @@
<div class="tip">{{t "You can only view or manage invitations that you sent." }}</div>
{{/unless}}
{{#if can_invite_others_to_realm}}
<a class="invite-user-link" href="#invite"><i class="fa fa-user-plus" aria-hidden="true"></i>{{t "Invite more users" }}</a>
<a class="invite-user-link" role="button"><i class="fa fa-user-plus" aria-hidden="true"></i>{{t "Invite more users" }}</a>
{{/if}}
<div class="settings_panel_list_header">