Skip to content

Commit

Permalink
todo_list: Add option for modal to create todo-lists.
Browse files Browse the repository at this point in the history
Previously, the `/todo` slash command was the sole method for
creating todos. Now, a button has been introduced to launch a modal
for creating todo-lists directly from the compose box.
This button becomes enabled only when the compose box is empty,
thereby avoiding complexities associated with losing or having to
save drafts of any messages already being composed.

The modal features a form that, upon submission,
generates a message using the `/todo` syntax and the data
inputted in the form. Subsequently, the content of the compose box
is set to this message, which the user can then send.

Added polls and todo lists into their own section on the right,
positioned just before the `?` icon.

This modal closely parallels the UI for adding a poll.

Fixes: zulip#29779.
  • Loading branch information
sujalshah-bit committed Apr 22, 2024
1 parent bc8e6a8 commit d516a2b
Show file tree
Hide file tree
Showing 13 changed files with 205 additions and 24 deletions.
1 change: 1 addition & 0 deletions tools/test-js-with-node
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,7 @@ EXEMPT_FILES = make_set(
"web/src/pm_list.ts",
"web/src/pm_list_dom.ts",
"web/src/poll_modal.ts",
"web/src/todo_list_modal.ts",
"web/src/poll_widget.ts",
"web/src/popover_menus.ts",
"web/src/popover_menus_data.js",
Expand Down
1 change: 1 addition & 0 deletions web/shared/icons/todo-list.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions web/shared/icons/todo-list.svg:Zone.Identifier
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[ZoneTransfer]
ZoneId=3
HostUrl=https://chat.zulip.org/user_uploads/2/e1/-VNXP7pLNPZ28w0swnj00_nW/todo-list.svg
1 change: 1 addition & 0 deletions web/src/compose.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export function clear_compose_box() {
compose_ui.hide_compose_spinner();
scheduled_messages.reset_selected_schedule_timestamp();
$(".compose_control_button_container:has(.add-poll)").removeClass("disabled-on-hover");
$(".compose_control_button_container:has(.add-todo-list)").removeClass("disabled-on-hover");
}

export function send_message_success(request, data) {
Expand Down
46 changes: 46 additions & 0 deletions web/src/compose_setup.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import $ from "jquery";

import {unresolve_name} from "../shared/src/resolved_topic";
import render_add_poll_modal from "../templates/add_poll_modal.hbs";
import render_add_todo_list_modal from "../templates/add_todo_list_modal.hbs";

import * as compose from "./compose";
import * as compose_actions from "./compose_actions";
Expand Down Expand Up @@ -29,6 +30,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 todo_list_modal from "./todo_list_modal";
import * as ui_report from "./ui_report";
import * as upload from "./upload";
import * as user_topics from "./user_topics";
Expand Down Expand Up @@ -85,8 +87,10 @@ export function initialize() {
// The poll widget requires an empty compose box.
if (compose_text_length > 0) {
$(".add-poll").parent().addClass("disabled-on-hover");
$(".add-todo-list").parent().addClass("disabled-on-hover");
} else {
$(".add-poll").parent().removeClass("disabled-on-hover");
$(".add-todo-list").parent().removeClass("disabled-on-hover");
}
});

Expand Down Expand Up @@ -401,6 +405,48 @@ export function initialize() {
});
});

$("body").on(
"click",
".compose_control_button_container:not(.disabled) .add-todo-list",
(e) => {
e.preventDefault();
e.stopPropagation();

function validate_input() {
const title = $("#todo-title-input").val().trim();

if (title === "") {
ui_report.error(
$t_html({defaultMessage: "Please enter a title."}),
undefined,
$("#dialog_error"),
);
return false;
}
return true;
}

dialog_widget.launch({
html_heading: $t_html({defaultMessage: "Create a collaborative to-do list"}),
html_body: render_add_todo_list_modal(),
html_submit_button: $t_html({defaultMessage: "Create to-do list"}),
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 todo_message_content = todo_list_modal.frame_todo_message_content();
compose_ui.insert_syntax_and_focus(todo_message_content);
},
validate_input,
form_id: "add-todo-form",
id: "add-todo-modal",
post_render: todo_list_modal.todo_list_tasks_setup,
help_link: "https://zulip.com/help/collaborative-to-do-lists",
});
},
);

$("#compose").on("click", ".markdown_preview", (e) => {
e.preventDefault();
e.stopPropagation();
Expand Down
15 changes: 15 additions & 0 deletions web/src/tippyjs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,21 @@ export function initialize(): void {
return false;
},
});
delegate("body", {
target: "#add-todo-modal .dialog_submit_button_container",
appendTo: () => document.body,
onShow(instance) {
const content = $t({defaultMessage: "Please enter a title."});
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",
Expand Down
79 changes: 79 additions & 0 deletions web/src/todo_list_modal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import $ from "jquery";
import SortableJS from "sortablejs";

import render_todo_modal_task from "../templates/todo_modal_task.hbs";

function create_option_row($last_option_row_input: JQuery): void {
const row_html = render_todo_modal_task();
const $row_container = $last_option_row_input.closest(".simplebar-content");
$row_container.append($(row_html));
}

function add_option_row(e: JQuery.TriggeredEvent): void {
// 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: JQuery.ClickEvent): void {
const $row = $(e.target).closest(".option-row");
$row.remove();
}

export function todo_list_tasks_setup(): void {
const $todo_options_list = $("#add-todo-form .todo-options-list");
const $submit_button = $("#add-todo-modal .dialog_submit_button");
const $title_input = $<HTMLInputElement>("#add-todo-form input#todo-title-input");

// Disable the submit button if the title is empty.
$submit_button.prop("disabled", true);
$title_input.on("input", () => {
if ($title_input.val()!.trim() !== "") {
$submit_button.prop("disabled", false);
} else {
$submit_button.prop("disabled", true);
}
});

$todo_options_list.on("input", "input.todo-input", add_option_row);
$todo_options_list.on("input", "input.todo-description-input", add_option_row);
$todo_options_list.on("click", "button.delete-option", delete_option_row);

// setTimeout is needed to here to give time for simplebar to initialise
setTimeout(() => {
SortableJS.create($("#add-todo-form .todo-options-list .simplebar-content")[0], {
onUpdate() {
// Do nothing on drag; the order is only processed on submission.
},
// 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_todo_message_content(): string {
const title = $<HTMLInputElement>("input#todo-title-input").val()?.toString().trim() ?? "";
const todo_str = title ? `/todo ${title}\n` : "";

const todos: string[] = [];

$(".option-row").each(function () {
const todo_name = $(this).find(".todo-input").val()?.toString().trim() ?? "";
const todo_description =
$(this).find(".todo-description-input").val()?.toString().trim() ?? "";

if (todo_name) {
todos.push(`${todo_name}: ${todo_description}`);
}
});

return todo_str + todos.join("\n");
}
42 changes: 23 additions & 19 deletions web/styles/modal.css
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,8 @@
}
}

#add-poll-modal {
#add-poll-modal,
#add-todo-modal {
/* this height allows 3-4 option rows
to fit in without need for scrolling */
height: 450px;
Expand All @@ -405,27 +406,32 @@
}
}

#add-poll-form {
#add-poll-form,
#add-todo-form {
display: flex;
flex-direction: column;
overflow: hidden;
height: 100%;

.poll-label {
.poll-label,
.todo-label {
font-weight: bold;
margin: 5px 0;
}

.poll-question-input-container {
.poll-question-input-container,
.todo-title-input-container {
display: flex;
margin-bottom: 10px;

#poll-question-input {
#poll-question-input,
#todo-title-input {
flex-grow: 1;
}
}

.poll-options-list {
.poll-options-list,
.todo-options-list {
margin: 0;
height: 0;
overflow: auto;
Expand All @@ -444,24 +450,22 @@
color: hsl(0deg 0% 75%);
}

.poll-option-input {
.poll-option-input,
.todo-title-input {
flex-grow: 1;
}
}

.option-row:first-child {
margin-top: 0;
}

.option-row:last-child {
cursor: default;

.delete-option {
visibility: hidden;
&:first-child {
margin-top: 0;
}

.drag-icon {
visibility: hidden;
&:last-child {
cursor: default;

.delete-option,
.drag-icon {
visibility: hidden;
}
}
}
}
Expand Down
13 changes: 13 additions & 0 deletions web/templates/add_todo_list_modal.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<form id="add-todo-form" class="new-style">
<label class="todo-label">{{t "To-do list title"}}</label>
<div class="todo-title-input-container">
<input type="text" id="todo-title-input" class="modal_text_input" placeholder="{{t 'Task list'}}" />
</div>
<label class="todo-label">{{t "Tasks"}}</label>
<p>{{t "Anyone can add more tasks after the to-do list is posted."}}</p>
<ul class="todo-options-list" data-simplebar>
{{> todo_modal_task }}
{{> todo_modal_task }}
{{> todo_modal_task }}
</ul>
</form>
5 changes: 0 additions & 5 deletions web/templates/compose_control_buttons.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,6 @@
<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 zulip-icon zulip-icon-time 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 zulip-icon zulip-icon-poll 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>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,12 @@
<a role="button" data-format-type="latex" class="compose_control_button zulip-icon zulip-icon-math formatting_button" aria-label="{{t 'Math (LaTeX)' }}" {{#unless preview_mode_on}} tabindex=0 {{/unless}} data-tippy-content="{{t 'Math (LaTeX)' }}"></a>
<div class="divider">|</div>
</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 zulip-icon zulip-icon-poll add-poll" aria-label="{{t 'Add poll' }}" tabindex=0></a>
</div>
<div class="compose_control_button_container preview_mode_disabled" data-tooltip-template-id="add-todo-tooltip" data-tippy-maxWidth="none">
<a role="button" class="compose_control_button zulip-icon zulip-icon-todo-list add-todo-list" aria-label="{{t 'Add todo' }}" tabindex=0></a>
</div>
{{/unless}}
<a role="button" class="compose_control_button compose_help_button zulip-icon zulip-icon-question" tabindex=0 data-tippy-content="{{t 'Message formatting' }}" data-overlay-trigger="message-formatting"></a>
9 changes: 9 additions & 0 deletions web/templates/todo_modal_task.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<!-- todo_modal_task.hbs -->
<li class="option-row">
<i class="zulip-icon zulip-icon-grip-vertical drag-icon"></i>
<input type="text" class="todo-input modal_text_input" placeholder="{{t 'New task'}}" />
<input type="text" class="todo-description-input modal_text_input" placeholder="{{t 'Task description (optional)'}}" />
<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>
6 changes: 6 additions & 0 deletions web/templates/tooltip_templates.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@
<span class="tooltip-inner-content italic">{{t "A poll must be an entire message." }}</span>
</div>
</template>
<template id="add-todo-tooltip">
<div>
<span>{{t "Add todo" }}</span><br/>
<span class="tooltip-inner-content italic">{{t "A todo must be an entire message." }}</span>
</div>
</template>
<template id="delete-draft-tooltip-template">
{{t 'Delete draft' }}
{{tooltip_hotkey_hints "Backspace"}}
Expand Down

0 comments on commit d516a2b

Please sign in to comment.