mirror of https://github.com/zulip/zulip.git
polls: Add option for modal to create polls.
Earlier the `/poll` slash command was the only way to create polls. To increase user friendliness with a GUI, a button to launch a modal to create a poll, has been added to the compose box. This button is enabled only when the compose box is empty, to avoid complexities with losing / having to save as draft any message already being composed. The modal has a form which on submission frames a message using the `/poll` syntax and the data input in the form, and sets the content of the compose box to that message, which the user can then send. The question field is mandatory for form submission. Fixes: #20304.
This commit is contained in:
parent
084718b776
commit
143db56992
|
@ -163,6 +163,7 @@ EXEMPT_FILES = make_set(
|
|||
"web/src/plotly.js.d.ts",
|
||||
"web/src/pm_list.js",
|
||||
"web/src/pm_list_dom.ts",
|
||||
"web/src/poll_modal.js",
|
||||
"web/src/poll_widget.ts",
|
||||
"web/src/popover_menus.js",
|
||||
"web/src/popover_menus_data.js",
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 2.2 KiB |
|
@ -117,6 +117,7 @@ export function clear_compose_box() {
|
|||
compose_banner.clear_uploads();
|
||||
compose_ui.hide_compose_spinner();
|
||||
scheduled_messages.reset_selected_schedule_timestamp();
|
||||
$(".compose_control_button_container:has(.add-poll)").removeClass("disabled-on-hover");
|
||||
}
|
||||
|
||||
export function send_message_success(request, data) {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import $ from "jquery";
|
||||
|
||||
import render_add_poll_modal from "../templates/add_poll_modal.hbs";
|
||||
|
||||
import * as compose from "./compose";
|
||||
import * as compose_actions from "./compose_actions";
|
||||
import * as compose_banner from "./compose_banner";
|
||||
|
@ -9,10 +11,13 @@ import * as compose_recipient from "./compose_recipient";
|
|||
import * as compose_state from "./compose_state";
|
||||
import * as compose_ui from "./compose_ui";
|
||||
import * as compose_validate from "./compose_validate";
|
||||
import * as dialog_widget from "./dialog_widget";
|
||||
import * as flatpickr from "./flatpickr";
|
||||
import {$t_html} from "./i18n";
|
||||
import * as message_edit from "./message_edit";
|
||||
import * as narrow from "./narrow";
|
||||
import {page_params} from "./page_params";
|
||||
import * as poll_modal from "./poll_modal";
|
||||
import * as popovers from "./popovers";
|
||||
import * as resize from "./resize";
|
||||
import * as rows from "./rows";
|
||||
|
@ -23,6 +28,7 @@ import * as stream_settings_components from "./stream_settings_components";
|
|||
import * as sub_store from "./sub_store";
|
||||
import * as subscriber_api from "./subscriber_api";
|
||||
import {get_timestamp_for_flatpickr} from "./timerender";
|
||||
import * as ui_report from "./ui_report";
|
||||
import * as upload from "./upload";
|
||||
import * as user_topics from "./user_topics";
|
||||
|
||||
|
@ -74,6 +80,13 @@ export function initialize() {
|
|||
} else {
|
||||
$("#compose_close").attr("data-tooltip-template-id", "compose_close_tooltip_template");
|
||||
}
|
||||
|
||||
// The poll widget requires an empty compose box.
|
||||
if (compose_text_length > 0) {
|
||||
$(".add-poll").parent().addClass("disabled-on-hover");
|
||||
} else {
|
||||
$(".add-poll").parent().removeClass("disabled-on-hover");
|
||||
}
|
||||
});
|
||||
|
||||
$("#compose form").on("submit", (e) => {
|
||||
|
@ -325,6 +338,44 @@ export function initialize() {
|
|||
}
|
||||
});
|
||||
|
||||
$("body").on("click", ".compose_control_button_container:not(.disabled) .add-poll", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
function validate_input() {
|
||||
const question = $("#poll-question-input").val().trim();
|
||||
|
||||
if (question === "") {
|
||||
ui_report.error(
|
||||
$t_html({defaultMessage: "Please enter a question."}),
|
||||
undefined,
|
||||
$("#dialog_error"),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
dialog_widget.launch({
|
||||
html_heading: $t_html({defaultMessage: "Create a poll"}),
|
||||
html_body: render_add_poll_modal(),
|
||||
html_submit_button: $t_html({defaultMessage: "Add poll"}),
|
||||
close_on_submit: true,
|
||||
on_click(e) {
|
||||
// frame a message using data input in modal, then populate the compose textarea with it
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const poll_message_content = poll_modal.frame_poll_message_content();
|
||||
compose_ui.insert_syntax_and_focus(poll_message_content);
|
||||
},
|
||||
validate_input,
|
||||
form_id: "add-poll-form",
|
||||
id: "add-poll-modal",
|
||||
post_render: poll_modal.poll_options_setup,
|
||||
help_link: "https://zulip.com/help/create-a-poll",
|
||||
});
|
||||
});
|
||||
|
||||
$("#compose").on("click", ".markdown_preview", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
import $ from "jquery";
|
||||
import {Sortable} from "sortablejs";
|
||||
|
||||
import render_poll_modal_option from "../templates/poll_modal_option.hbs";
|
||||
|
||||
function create_option_row($last_option_row_input) {
|
||||
const row = render_poll_modal_option();
|
||||
const $row_container = $last_option_row_input.closest(".simplebar-content");
|
||||
$row_container.append(row);
|
||||
}
|
||||
|
||||
function add_option_row(e) {
|
||||
// if the option triggering the input event e is not the last,
|
||||
// that is, its next sibling has the class `option-row`, we
|
||||
// do not add a new option row and return from this function
|
||||
// This handles a case when the next empty input row is already
|
||||
// added and user is updating the above row(s).
|
||||
if ($(e.target).closest(".option-row").next().hasClass("option-row")) {
|
||||
return;
|
||||
}
|
||||
create_option_row($(e.target));
|
||||
}
|
||||
|
||||
function delete_option_row(e) {
|
||||
const $row = $(e.target).closest(".option-row");
|
||||
$row.remove();
|
||||
}
|
||||
|
||||
export function poll_options_setup() {
|
||||
const $poll_options_list = $("#add-poll-form .poll-options-list");
|
||||
const $submit_button = $("#add-poll-modal .dialog_submit_button");
|
||||
const $question_input = $("#add-poll-form #poll-question-input");
|
||||
|
||||
// Disable the submit button if the question is empty.
|
||||
$submit_button.prop("disabled", true);
|
||||
$question_input.on("input", () => {
|
||||
if ($question_input.val().trim() !== "") {
|
||||
$submit_button.prop("disabled", false);
|
||||
} else {
|
||||
$submit_button.prop("disabled", true);
|
||||
}
|
||||
});
|
||||
|
||||
$poll_options_list.on("input", "input.poll-option-input", add_option_row);
|
||||
$poll_options_list.on("click", "button.delete-option", delete_option_row);
|
||||
|
||||
// setTimeout is needed to here to give time for simplebar to initialise
|
||||
setTimeout(() => {
|
||||
Sortable.create($("#add-poll-form .poll-options-list .simplebar-content")[0], {
|
||||
onUpdate() {},
|
||||
// We don't want the last (empty) row to be draggable, as a new row
|
||||
// is added on input event of the last row.
|
||||
filter: "input, .option-row:last-child",
|
||||
preventOnFilter: false,
|
||||
});
|
||||
}, 0);
|
||||
}
|
||||
|
||||
export function frame_poll_message_content() {
|
||||
const question = $("#poll-question-input").val().trim();
|
||||
const options = $(".poll-option-input")
|
||||
.map(function () {
|
||||
return $(this).val().trim();
|
||||
})
|
||||
.toArray()
|
||||
.filter(Boolean);
|
||||
return "/poll " + question + "\n" + options.join("\n");
|
||||
}
|
|
@ -209,6 +209,22 @@ export function initialize(): void {
|
|||
},
|
||||
});
|
||||
|
||||
delegate("body", {
|
||||
target: "#add-poll-modal .dialog_submit_button_container",
|
||||
appendTo: () => document.body,
|
||||
onShow(instance) {
|
||||
const content = $t({defaultMessage: "Please enter a question."});
|
||||
const $elem = $(instance.reference);
|
||||
// Show tooltip to enter question only if submit button is disabled
|
||||
// (due to question field being empty).
|
||||
if ($elem.find(".dialog_submit_button").is(":disabled")) {
|
||||
instance.setContent(content);
|
||||
return undefined;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
$("body").on(
|
||||
"blur",
|
||||
".message_control_button, .delete-selected-drafts-button-container",
|
||||
|
|
|
@ -994,6 +994,15 @@ textarea.new_message_textarea,
|
|||
}
|
||||
}
|
||||
|
||||
.compose_control_button_container.disabled-on-hover:hover {
|
||||
opacity: 0.3;
|
||||
cursor: not-allowed;
|
||||
|
||||
.compose_control_button {
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.fa-eye {
|
||||
position: relative;
|
||||
top: -0.7px;
|
||||
|
|
|
@ -385,3 +385,81 @@
|
|||
0 0 8px hsl(206deg 80% 62% / 60%);
|
||||
}
|
||||
}
|
||||
|
||||
#add-poll-modal {
|
||||
/* this height allows 3-4 option rows
|
||||
to fit in without need for scrolling */
|
||||
height: 450px;
|
||||
overflow: hidden;
|
||||
|
||||
.modal__content {
|
||||
flex-grow: 1;
|
||||
|
||||
.simplebar-content {
|
||||
box-sizing: border-box;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
#add-poll-form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
|
||||
.poll-label {
|
||||
font-weight: bold;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.poll-question-input-container {
|
||||
display: flex;
|
||||
margin-bottom: 10px;
|
||||
|
||||
#poll-question-input {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.poll-options-list {
|
||||
margin: 0;
|
||||
height: 0;
|
||||
overflow: auto;
|
||||
flex-grow: 1;
|
||||
|
||||
.option-row {
|
||||
list-style-type: none;
|
||||
cursor: move;
|
||||
margin-top: 10px;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
|
||||
.drag-icon {
|
||||
color: hsl(0deg 0% 75%);
|
||||
}
|
||||
|
||||
.poll-option-input {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.option-row:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.option-row:last-child {
|
||||
cursor: default;
|
||||
|
||||
.delete-option {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.drag-icon {
|
||||
visibility: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<form id="add-poll-form" class="new-style">
|
||||
<label class="poll-label">{{t "Question"}}</label>
|
||||
<div class="poll-question-input-container">
|
||||
<input type="text" id="poll-question-input" class="modal_text_input" placeholder="{{t 'Your question'}}" />
|
||||
</div>
|
||||
<label class="poll-label">{{t "Options"}}</label>
|
||||
<p>{{t "Anyone can add more options after the poll is posted."}}</p>
|
||||
<ul class="poll-options-list" data-simplebar>
|
||||
{{> poll_modal_option }}
|
||||
{{> poll_modal_option }}
|
||||
{{> poll_modal_option }}
|
||||
</ul>
|
||||
</form>
|
|
@ -24,6 +24,11 @@
|
|||
<div class="compose_control_button_container preview_mode_disabled" data-tooltip-template-id="add-global-time-tooltip" data-tippy-maxWidth="none">
|
||||
<a role="button" class="compose_control_button fa fa-clock-o time_pick" aria-label="{{t 'Add global time' }}" tabindex=0></a>
|
||||
</div>
|
||||
{{#unless message_id}}
|
||||
<div class="compose_control_button_container preview_mode_disabled" data-tooltip-template-id="add-poll-tooltip" data-tippy-maxWidth="none">
|
||||
<a role="button" class="compose_control_button fa fa-hand-paper-o add-poll" aria-label="{{t 'Add poll' }}" tabindex=0></a>
|
||||
</div>
|
||||
{{/unless}}
|
||||
<div class="compose_control_button_container {{#unless giphy_enabled }}hide{{/unless}} preview_mode_disabled" data-tippy-content="{{t 'Add GIF' }}">
|
||||
<a role="button" class="compose_control_button compose_gif_icon zulip-icon zulip-icon-gif" aria-label="{{t 'Add GIF' }}" tabindex=0></a>
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
<li class="option-row">
|
||||
<i class="zulip-icon zulip-icon-grip-vertical drag-icon"></i>
|
||||
<input type="text" class="poll-option-input modal_text_input" placeholder="{{t 'New option' }}" />
|
||||
<button type="button" class="button rounded small btn-secondary delete-option" title="{{t 'Delete' }}">
|
||||
<i class="fa fa-trash-o" aria-hidden="true"></i>
|
||||
</button>
|
||||
</li>
|
|
@ -59,6 +59,12 @@
|
|||
<div class="tooltip-inner-content italic">{{t "Everyone sees global times in their own time zone." }}</div>
|
||||
</div>
|
||||
</template>
|
||||
<template id="add-poll-tooltip">
|
||||
<div>
|
||||
<span>{{t "Add poll" }}</span><br/>
|
||||
<span class="tooltip-inner-content italic">{{t "A poll must be an entire message." }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template id="delete-draft-tooltip-template">
|
||||
{{t 'Delete draft' }}
|
||||
{{tooltip_hotkey_hints "Backspace"}}
|
||||
|
|
Loading…
Reference in New Issue