2023-04-07 04:58:17 +02:00
|
|
|
/* Compose box module responsible for the message's recipient */
|
|
|
|
|
2023-03-31 06:27:36 +02:00
|
|
|
import $ from "jquery";
|
|
|
|
import _ from "lodash";
|
|
|
|
|
2023-05-07 14:45:04 +02:00
|
|
|
import render_inline_decorated_stream_name from "../templates/inline_decorated_stream_name.hbs";
|
|
|
|
|
2023-04-23 14:18:48 +02:00
|
|
|
import * as compose_banner from "./compose_banner";
|
2023-03-31 06:27:36 +02:00
|
|
|
import * as compose_fade from "./compose_fade";
|
2023-04-27 23:45:30 +02:00
|
|
|
import * as compose_pm_pill from "./compose_pm_pill";
|
2023-03-31 06:27:36 +02:00
|
|
|
import * as compose_state from "./compose_state";
|
2023-04-27 23:45:30 +02:00
|
|
|
import * as compose_ui from "./compose_ui";
|
2023-03-31 06:27:36 +02:00
|
|
|
import * as compose_validate from "./compose_validate";
|
2023-05-07 14:45:04 +02:00
|
|
|
import * as dropdown_widget from "./dropdown_widget";
|
2023-03-31 06:27:36 +02:00
|
|
|
import {$t} from "./i18n";
|
|
|
|
import * as narrow_state from "./narrow_state";
|
2023-04-22 23:18:32 +02:00
|
|
|
import {page_params} from "./page_params";
|
|
|
|
import * as settings_config from "./settings_config";
|
2023-03-31 06:27:36 +02:00
|
|
|
import * as stream_bar from "./stream_bar";
|
|
|
|
import * as stream_data from "./stream_data";
|
2023-05-01 23:17:55 +02:00
|
|
|
import * as ui_util from "./ui_util";
|
2023-03-31 06:27:36 +02:00
|
|
|
import * as util from "./util";
|
|
|
|
|
2023-05-07 14:45:04 +02:00
|
|
|
// selected_recipient_id is the current state for the stream picker widget:
|
|
|
|
// "" -> stream message but no stream is selected
|
|
|
|
// integer -> stream id of the selected stream.
|
|
|
|
// "direct" -> Direct message is selected.
|
|
|
|
export let selected_recipient_id = "";
|
|
|
|
export const DIRECT_MESSAGE_ID = "direct";
|
2023-03-31 06:27:36 +02:00
|
|
|
|
2023-05-07 14:45:04 +02:00
|
|
|
export function set_selected_recipient_id(recipient_id) {
|
|
|
|
selected_recipient_id = recipient_id;
|
|
|
|
on_compose_select_recipient_update();
|
|
|
|
}
|
2023-04-15 03:35:23 +02:00
|
|
|
|
2023-03-31 06:27:36 +02:00
|
|
|
function composing_to_current_topic_narrow() {
|
|
|
|
return (
|
|
|
|
util.lower_same(compose_state.stream_name(), narrow_state.stream() || "") &&
|
|
|
|
util.lower_same(compose_state.topic(), narrow_state.topic() || "")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
function composing_to_current_private_message_narrow() {
|
|
|
|
const compose_state_recipient = compose_state.private_message_recipient();
|
|
|
|
const narrow_state_recipient = narrow_state.pm_emails_string();
|
|
|
|
return (
|
|
|
|
compose_state_recipient &&
|
|
|
|
narrow_state_recipient &&
|
|
|
|
_.isEqual(
|
|
|
|
compose_state_recipient
|
|
|
|
.split(",")
|
|
|
|
.map((s) => s.trim())
|
|
|
|
.sort(),
|
|
|
|
narrow_state_recipient
|
|
|
|
.split(",")
|
|
|
|
.map((s) => s.trim())
|
|
|
|
.sort(),
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
export function update_narrow_to_recipient_visibility() {
|
|
|
|
const message_type = compose_state.get_message_type();
|
|
|
|
if (message_type === "stream") {
|
|
|
|
const stream_name = compose_state.stream_name();
|
|
|
|
const stream_exists = Boolean(stream_data.get_stream_id(stream_name));
|
|
|
|
|
|
|
|
if (
|
|
|
|
stream_exists &&
|
|
|
|
!composing_to_current_topic_narrow() &&
|
|
|
|
compose_state.has_full_recipient()
|
|
|
|
) {
|
|
|
|
$(".narrow_to_compose_recipients").toggleClass("invisible", false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if (message_type === "private") {
|
|
|
|
const recipients = compose_state.private_message_recipient();
|
|
|
|
if (
|
|
|
|
recipients &&
|
|
|
|
!composing_to_current_private_message_narrow() &&
|
|
|
|
compose_state.has_full_recipient()
|
|
|
|
) {
|
|
|
|
$(".narrow_to_compose_recipients").toggleClass("invisible", false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$(".narrow_to_compose_recipients").toggleClass("invisible", true);
|
|
|
|
}
|
|
|
|
|
|
|
|
function update_fade() {
|
|
|
|
if (!compose_state.composing()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const msg_type = compose_state.get_message_type();
|
|
|
|
|
|
|
|
// It's possible that the new topic is not a resolved topic
|
|
|
|
// so we clear the older warning.
|
|
|
|
compose_validate.clear_topic_resolved_warning();
|
|
|
|
|
|
|
|
compose_validate.warn_if_topic_resolved();
|
|
|
|
compose_fade.set_focused_recipient(msg_type);
|
|
|
|
compose_fade.update_all();
|
|
|
|
}
|
|
|
|
|
|
|
|
export function update_on_recipient_change() {
|
|
|
|
update_fade();
|
|
|
|
update_narrow_to_recipient_visibility();
|
|
|
|
}
|
|
|
|
|
2023-04-23 14:18:48 +02:00
|
|
|
export function check_stream_posting_policy_for_compose_box(stream_name) {
|
|
|
|
const stream = stream_data.get_sub_by_name(stream_name);
|
|
|
|
if (!stream) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const can_post_messages_in_stream = stream_data.can_post_messages_in_stream(stream);
|
|
|
|
if (!can_post_messages_in_stream) {
|
|
|
|
$(".compose_right_float_container").addClass("disabled-compose-send-button-container");
|
|
|
|
compose_banner.show_error_message(
|
|
|
|
$t({
|
|
|
|
defaultMessage: "You do not have permission to post in this stream.",
|
|
|
|
}),
|
|
|
|
compose_banner.CLASSNAMES.no_post_permissions,
|
2023-05-02 11:05:37 +02:00
|
|
|
$("#compose_banners"),
|
2023-04-23 14:18:48 +02:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
$(".compose_right_float_container").removeClass("disabled-compose-send-button-container");
|
|
|
|
compose_banner.clear_errors();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-04-15 03:35:23 +02:00
|
|
|
function switch_message_type(message_type) {
|
|
|
|
$("#compose-content .alert").hide();
|
|
|
|
|
|
|
|
compose_state.set_message_type(message_type);
|
|
|
|
|
|
|
|
const opts = {
|
|
|
|
message_type,
|
|
|
|
stream: compose_state.stream_name(),
|
|
|
|
topic: compose_state.topic(),
|
|
|
|
private_message_recipient: compose_state.private_message_recipient(),
|
|
|
|
};
|
2023-04-27 23:45:30 +02:00
|
|
|
update_compose_for_message_type(message_type, opts);
|
|
|
|
update_placeholder_text();
|
|
|
|
compose_ui.set_focus(message_type, opts);
|
|
|
|
}
|
|
|
|
|
2023-05-07 14:45:04 +02:00
|
|
|
export function update_compose_for_message_type(message_type, opts) {
|
2023-04-27 23:45:30 +02:00
|
|
|
if (message_type === "stream") {
|
|
|
|
$("#compose-direct-recipient").hide();
|
|
|
|
$("#stream_message_recipient_topic").show();
|
|
|
|
$("#stream_toggle").addClass("active");
|
|
|
|
$("#private_message_toggle").removeClass("active");
|
|
|
|
$("#compose-recipient").removeClass("compose-recipient-direct-selected");
|
2023-05-07 14:45:04 +02:00
|
|
|
const stream = stream_data.get_sub_by_name(opts.stream);
|
|
|
|
if (stream === undefined) {
|
|
|
|
$("#compose_select_recipient_name").text($t({defaultMessage: "Select a stream"}));
|
|
|
|
} else {
|
|
|
|
$("#compose_select_recipient_name").html(
|
|
|
|
render_inline_decorated_stream_name({stream, show_colored_icon: true}),
|
|
|
|
);
|
|
|
|
}
|
2023-04-27 23:45:30 +02:00
|
|
|
} else {
|
|
|
|
$("#compose-direct-recipient").show();
|
|
|
|
$("#stream_message_recipient_topic").hide();
|
|
|
|
$("#stream_toggle").removeClass("active");
|
|
|
|
$("#private_message_toggle").addClass("active");
|
|
|
|
$("#compose-recipient").addClass("compose-recipient-direct-selected");
|
|
|
|
// TODO: When "Direct message" is selected, we show "DM" on the dropdown
|
|
|
|
// button. It would be nice if the dropdown supported a way to attach
|
|
|
|
// the "DM" button display string so we wouldn't have to manually change
|
|
|
|
// it here.
|
|
|
|
const direct_message_label = $t({defaultMessage: "DM"});
|
|
|
|
$("#compose_select_recipient_name").html(
|
|
|
|
`<i class="zulip-icon zulip-icon-users stream-privacy-type-icon"></i> ${direct_message_label}`,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
compose_banner.clear_errors();
|
|
|
|
compose_banner.clear_warnings();
|
2023-04-15 03:35:23 +02:00
|
|
|
}
|
|
|
|
|
2023-05-07 14:45:04 +02:00
|
|
|
export function on_compose_select_recipient_update() {
|
|
|
|
let message_type = "stream";
|
|
|
|
if (selected_recipient_id === DIRECT_MESSAGE_ID) {
|
|
|
|
message_type = "private";
|
|
|
|
}
|
|
|
|
compose_state.set_message_type(message_type);
|
|
|
|
if (message_type === "private") {
|
2023-04-15 03:35:23 +02:00
|
|
|
// TODO: In theory, we could do something more lightweight in
|
|
|
|
// the case it's already that value, but doing nothing would
|
|
|
|
// display the wrong and fail to update focus properly.
|
|
|
|
switch_message_type("private");
|
|
|
|
|
|
|
|
if (compose_state.private_message_recipient().length === 0) {
|
|
|
|
$("#private_message_recipient").trigger("focus").trigger("select");
|
|
|
|
}
|
|
|
|
} else {
|
2023-04-25 21:27:03 +02:00
|
|
|
const $stream_header_colorblock = $(
|
|
|
|
"#compose_recipient_selection_dropdown .stream_header_colorblock",
|
2023-04-15 03:35:23 +02:00
|
|
|
);
|
2023-05-07 14:45:04 +02:00
|
|
|
const stream_name = compose_state.stream_name();
|
|
|
|
stream_bar.decorate(stream_name, $stream_header_colorblock);
|
|
|
|
switch_message_type("stream");
|
2023-04-15 03:35:23 +02:00
|
|
|
// Always move focus to the topic input even if it's not empty,
|
|
|
|
// since it's likely the user will want to update the topic
|
|
|
|
// after updating the stream.
|
2023-05-01 23:17:55 +02:00
|
|
|
ui_util.place_caret_at_end($("#stream_message_recipient_topic")[0]);
|
2023-05-07 14:45:04 +02:00
|
|
|
check_stream_posting_policy_for_compose_box(stream_name);
|
2023-04-15 03:35:23 +02:00
|
|
|
}
|
2023-03-31 06:27:36 +02:00
|
|
|
update_on_recipient_change();
|
|
|
|
}
|
|
|
|
|
2023-05-07 14:45:04 +02:00
|
|
|
export function possibly_update_stream_name_in_compose(stream_id) {
|
|
|
|
if (selected_recipient_id === stream_id) {
|
|
|
|
on_compose_select_recipient_update();
|
|
|
|
}
|
2023-03-31 06:27:36 +02:00
|
|
|
}
|
|
|
|
|
2023-05-07 14:45:04 +02:00
|
|
|
function item_click_callback(event, dropdown) {
|
|
|
|
let recipient_id = $(event.currentTarget).attr("data-unique-id");
|
|
|
|
if (recipient_id !== DIRECT_MESSAGE_ID) {
|
|
|
|
recipient_id = Number.parseInt(recipient_id, 10);
|
2023-03-31 06:27:36 +02:00
|
|
|
}
|
2023-05-07 14:45:04 +02:00
|
|
|
set_selected_recipient_id(recipient_id);
|
|
|
|
dropdown.hide();
|
|
|
|
event.preventDefault();
|
|
|
|
event.stopPropagation();
|
2023-03-31 06:27:36 +02:00
|
|
|
}
|
|
|
|
|
2023-04-22 23:40:14 +02:00
|
|
|
function get_options_for_recipient_widget() {
|
2023-04-15 03:35:23 +02:00
|
|
|
const options = stream_data
|
2023-03-31 06:27:36 +02:00
|
|
|
.subscribed_subs()
|
|
|
|
.map((stream) => ({
|
|
|
|
name: stream.name,
|
2023-05-07 14:45:04 +02:00
|
|
|
unique_id: stream.stream_id,
|
2023-03-31 06:27:36 +02:00
|
|
|
stream,
|
|
|
|
}))
|
|
|
|
.sort((a, b) => {
|
|
|
|
if (a.name.toLowerCase() < b.name.toLowerCase()) {
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
if (a.name.toLowerCase() > b.name.toLowerCase()) {
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
});
|
2023-04-15 03:35:23 +02:00
|
|
|
|
|
|
|
const direct_messages_option = {
|
2023-04-25 01:24:50 +02:00
|
|
|
is_direct_message: true,
|
2023-05-07 14:45:04 +02:00
|
|
|
unique_id: DIRECT_MESSAGE_ID,
|
|
|
|
name: $t({defaultMessage: "Direct message"}),
|
2023-04-15 03:35:23 +02:00
|
|
|
};
|
2023-05-07 14:45:04 +02:00
|
|
|
|
2023-04-22 23:18:32 +02:00
|
|
|
if (
|
|
|
|
page_params.realm_private_message_policy ===
|
|
|
|
settings_config.private_message_policy_values.by_anyone.code
|
|
|
|
) {
|
|
|
|
options.unshift(direct_messages_option);
|
|
|
|
} else {
|
|
|
|
options.push(direct_messages_option);
|
|
|
|
}
|
2023-04-15 03:35:23 +02:00
|
|
|
return options;
|
2023-04-22 23:40:14 +02:00
|
|
|
}
|
|
|
|
|
2023-05-07 14:45:04 +02:00
|
|
|
function compose_recipient_dropdown_on_show(dropdown) {
|
|
|
|
// Offset to display dropdown above compose.
|
|
|
|
let top_offset = 5;
|
|
|
|
const window_height = window.innerHeight;
|
|
|
|
const search_box_and_padding_height = 50;
|
|
|
|
// pixels above compose box.
|
|
|
|
const recipient_input_top = $("#compose_recipient_selection_dropdown").offset().top;
|
|
|
|
const top_space = recipient_input_top - top_offset - search_box_and_padding_height;
|
|
|
|
// pixels below compose starting from top of compose box.
|
|
|
|
const bottom_space = window_height - recipient_input_top - search_box_and_padding_height;
|
|
|
|
// Show dropdown on top / bottom based on available space.
|
|
|
|
let placement = "top-start";
|
|
|
|
if (bottom_space > top_space) {
|
|
|
|
placement = "bottom-start";
|
|
|
|
top_offset = -30;
|
|
|
|
}
|
|
|
|
const offset = [-10, top_offset];
|
|
|
|
dropdown.setProps({placement, offset});
|
|
|
|
const height = Math.min(
|
|
|
|
dropdown_widget.DEFAULT_DROPDOWN_HEIGHT,
|
|
|
|
Math.max(top_space, bottom_space),
|
|
|
|
);
|
|
|
|
const $popper = $(dropdown.popper);
|
2023-05-09 22:36:41 +02:00
|
|
|
$popper.find(".dropdown-list-wrapper").css("max-height", height + "px");
|
2023-05-07 14:45:04 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
export function open_compose_recipient_dropdown() {
|
|
|
|
$("#compose_select_recipient_widget").trigger("click");
|
|
|
|
}
|
|
|
|
|
|
|
|
function focus_compose_recipient() {
|
|
|
|
$("#compose_recipient_selection_dropdown").trigger("focus");
|
|
|
|
}
|
|
|
|
|
2023-04-22 23:40:14 +02:00
|
|
|
export function initialize() {
|
2023-05-07 14:45:04 +02:00
|
|
|
dropdown_widget.setup(
|
|
|
|
{
|
|
|
|
target: "#compose_select_recipient_widget",
|
|
|
|
},
|
|
|
|
get_options_for_recipient_widget,
|
|
|
|
item_click_callback,
|
|
|
|
{
|
|
|
|
on_show_callback: compose_recipient_dropdown_on_show,
|
|
|
|
on_exit_with_escape_callback: focus_compose_recipient,
|
|
|
|
// We want to focus on topic box if dropdown was closed via selecting an item.
|
|
|
|
focus_target_on_hidden: false,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
|
|
|
|
$("#compose_recipient_selection_dropdown").on("keydown", (e) => {
|
|
|
|
if (e.key === "Enter") {
|
|
|
|
open_compose_recipient_dropdown();
|
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
}
|
2023-03-31 06:27:36 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
// `keyup` isn't relevant for streams since it registers as a change only
|
|
|
|
// when an item in the dropdown is selected.
|
|
|
|
$("#stream_message_recipient_topic,#private_message_recipient").on(
|
|
|
|
"keyup",
|
|
|
|
update_on_recipient_change,
|
|
|
|
);
|
2023-04-18 05:13:50 +02:00
|
|
|
// changes for the stream dropdown are handled in on_compose_select_recipient_update
|
2023-03-31 06:27:36 +02:00
|
|
|
$("#stream_message_recipient_topic,#private_message_recipient").on("change", () => {
|
|
|
|
update_on_recipient_change();
|
|
|
|
compose_state.set_recipient_edited_manually(true);
|
|
|
|
});
|
|
|
|
}
|
2023-04-27 23:45:30 +02:00
|
|
|
|
|
|
|
export function update_placeholder_text() {
|
|
|
|
// Change compose placeholder text only if compose box is open.
|
|
|
|
if (!$("#compose-textarea").is(":visible")) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const opts = {
|
|
|
|
message_type: compose_state.get_message_type(),
|
|
|
|
stream: compose_state.stream_name(),
|
|
|
|
topic: compose_state.topic(),
|
|
|
|
// TODO: to remove a circular import, PM recipient needs
|
|
|
|
// to be calculated in compose_state instead of compose_pm_pill.
|
|
|
|
private_message_recipient: compose_pm_pill.get_emails(),
|
|
|
|
};
|
|
|
|
|
|
|
|
$("#compose-textarea").attr("placeholder", compose_ui.compute_placeholder_text(opts));
|
|
|
|
}
|