diff --git a/.eslintrc.json b/.eslintrc.json index 7e4c829a2a..c20f92f8c1 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -153,6 +153,7 @@ "invite": false, "colorspace": false, "reactions": false, + "reminder": false, "tutorial": false, "templates": false, "alert_words": false, diff --git a/frontend_tests/node_tests/compose.js b/frontend_tests/node_tests/compose.js index 1b18c4d123..0db4594673 100644 --- a/frontend_tests/node_tests/compose.js +++ b/frontend_tests/node_tests/compose.js @@ -46,6 +46,9 @@ set_global('notifications', { clear_compose_notifications: noop, }); set_global('subs', {}); +set_global('reminder', { + is_deferred_delivery: noop, +}); // Setting these up so that we can test that links to uploads within messages are // automatically converted to server relative links. diff --git a/static/js/compose.js b/static/js/compose.js index 62c5dc2ac8..bff629690f 100644 --- a/static/js/compose.js +++ b/static/js/compose.js @@ -162,7 +162,7 @@ function create_message_object() { } return message; } -// Export for testing + exports.create_message_object = create_message_object; function compose_error(error_text, bad_input) { @@ -177,6 +177,8 @@ function compose_error(error_text, bad_input) { } } +exports.compose_error = compose_error; + function nonexistent_stream_reply_error() { $("#nonexistent_stream_reply_error").show(); $("#compose-reply-error-msg").html("There are no messages to reply to yet."); @@ -210,6 +212,8 @@ function clear_compose_box() { resize.resize_bottom_whitespace(); } +exports.clear_compose_box = clear_compose_box; + exports.send_message_success = function (local_id, message_id, locally_echoed) { if (!locally_echoed) { clear_compose_box(); @@ -279,102 +283,6 @@ exports.send_message = function send_message(request) { } }; -exports.deferred_message_types = { - scheduled: { - delivery_type: 'send_later', - test: /^\/schedule/, - slash_command: '/schedule', - }, - reminders: { - delivery_type: 'remind', - test: /^\/remind/, - slash_command: '/remind', - }, -}; - -function is_deferred_delivery(message_content) { - var reminders_test = exports.deferred_message_types.reminders.test; - var scheduled_test = exports.deferred_message_types.scheduled.test; - return (reminders_test.test(message_content) || - scheduled_test.test(message_content)); -} - -function patch_request_for_scheduling(request) { - var new_request = request; - var raw_message = request.content.split('\n'); - var command_line = raw_message[0]; - var message = raw_message.slice(1).join('\n'); - - var deferred_message_type = _.filter(exports.deferred_message_types, function (props) { - return command_line.match(props.test) !== null; - })[0]; - var command = command_line.match(deferred_message_type.test)[0]; - - var deliver_at = command_line.slice(command.length + 1); - - if (message.trim() === '' || deliver_at.trim() === '' || - command_line.slice(command.length, command.length + 1) !== ' ') { - - $("#compose-textarea").attr('disabled', false); - if (command_line.slice(command.length, command.length + 1) !== ' ') { - compose_error(i18n.t('Invalid slash command. Check if you are missing a space after the command.'), $('#compose-textarea')); - } else if (deliver_at.trim() === '') { - compose_error(i18n.t('Please specify time for your reminder.'), $('#compose-textarea')); - } else { - compose_error(i18n.t('Your reminder note is empty!'), $('#compose-textarea')); - } - return; - } - - new_request.content = message; - new_request.deliver_at = deliver_at; - new_request.delivery_type = deferred_message_type.delivery_type; - new_request.tz_guess = moment.tz.guess(); - return new_request; -} - -exports.schedule_message = function schedule_message(request, success, error) { - if (request === undefined) { - request = create_message_object(); - } - - if (request.type === "private") { - request.to = JSON.stringify(request.to); - } else { - request.to = JSON.stringify([request.to]); - } - - /* success and error callbacks are kind of a package deal here. When scheduling - a message either by means of slash command or from message feed, if we need to do - something special on success then we will also need to know if our request errored - and do something appropriate. Therefore we just check if success callback is not - defined and just assume request to be coming from compose box. This is correct - because we won't ever actually have success operate in different context than error. */ - if (success === undefined) { - success = function (data) { - notifications.notify_above_composebox('Scheduled your Message to be delivered at: ' + data.deliver_at); - $("#compose-textarea").attr('disabled', false); - clear_compose_box(); - }; - error = function (response) { - $("#compose-textarea").attr('disabled', false); - compose_error(response, $('#compose-textarea')); - }; - /* We are adding a disable on compose under this block since it actually - has its place with the branch of code which does stuff when slash command - is incoming from compose_box */ - $("#compose-textarea").attr('disabled', true); - } - - request = patch_request_for_scheduling(request); - - if (request === undefined) { - return; - } - - transmit.send_message(request, success, error); -}; - exports.enter_with_preview_open = function () { exports.clear_preview_area(); if (page_params.enter_sends) { @@ -396,8 +304,8 @@ exports.finish = function () { } var message_content = compose_state.message_content(); - if (is_deferred_delivery(message_content)) { - exports.schedule_message(); + if (reminder.is_deferred_delivery(message_content)) { + reminder.schedule_message(); } else { exports.send_message(); } @@ -620,7 +528,7 @@ function validate_private_message() { exports.validate = function () { $("#compose-send-button").attr('disabled', 'disabled').blur(); var message_content = compose_state.message_content(); - if (is_deferred_delivery(message_content)) { + if (reminder.is_deferred_delivery(message_content)) { show_sending_indicator('Scheduling...'); } else { show_sending_indicator(); diff --git a/static/js/popovers.js b/static/js/popovers.js index 5a560ea001..4be8c94e90 100644 --- a/static/js/popovers.js +++ b/static/js/popovers.js @@ -349,46 +349,6 @@ exports.toggle_actions_popover = function (element, id) { } }; -function do_set_reminder(msgid, timestamp) { - var message = current_msg_list.get(msgid); - var link_to_msg = narrow.by_conversation_and_time_uri(message, true); - var command = compose.deferred_message_types.reminders.slash_command; - var reminder_timestamp = timestamp; - var custom_msg = '[this message](' + link_to_msg + ') at ' + reminder_timestamp; - var reminder_msg_content = command + ' ' + reminder_timestamp + '\n' + custom_msg; - var reminder_message = { - type: "private", - content: reminder_msg_content, - sender_id: page_params.user_id, - stream: '', - subject: '', - }; - var recipient = page_params.email; - var emails = util.extract_pm_recipients(recipient); - reminder_message.to = emails; - reminder_message.reply_to = recipient; - reminder_message.private_message_recipient = recipient; - reminder_message.to_user_ids = people.email_list_to_user_ids_string(emails); - - var row = $("[zid='" + msgid + "']"); - - function success() { - row.find(".alert-msg") - .text(i18n.t("Reminder set!")) - .css("display", "block") - .delay(1000).fadeOut(300); - } - - function error() { - row.find(".alert-msg") - .text(i18n.t("Setting reminder failed!")) - .css("display", "block") - .delay(1000).fadeOut(300); - } - - compose.schedule_message(reminder_message, success, error); -} - exports.render_actions_remind_popover = function (element, id) { popovers.hide_all(); $(element).closest('.message_row').toggleClass('has_popover has_actions_popover'); @@ -754,7 +714,7 @@ exports.register_click_handlers = function () { function reminder_click_handler(datestr, e) { var id = $(".remind.custom").data('message-id'); - do_set_reminder(id, datestr); + reminder.do_set_reminder_for_message(id, datestr); popovers.hide_all(); e.stopPropagation(); e.preventDefault(); diff --git a/static/js/reminder.js b/static/js/reminder.js new file mode 100644 index 0000000000..2bdffd525e --- /dev/null +++ b/static/js/reminder.js @@ -0,0 +1,145 @@ +var reminder = (function () { + +var exports = {}; + +var deferred_message_types = { + scheduled: { + delivery_type: 'send_later', + test: /^\/schedule/, + slash_command: '/schedule', + }, + reminders: { + delivery_type: 'remind', + test: /^\/remind/, + slash_command: '/remind', + }, +}; + +exports.deferred_message_types = deferred_message_types; + +exports.is_deferred_delivery = function (message_content) { + var reminders_test = deferred_message_types.reminders.test; + var scheduled_test = deferred_message_types.scheduled.test; + return (reminders_test.test(message_content) || + scheduled_test.test(message_content)); +}; + +function patch_request_for_scheduling(request) { + var new_request = request; + var raw_message = request.content.split('\n'); + var command_line = raw_message[0]; + var message = raw_message.slice(1).join('\n'); + + var deferred_message_type = _.filter(deferred_message_types, function (props) { + return command_line.match(props.test) !== null; + })[0]; + var command = command_line.match(deferred_message_type.test)[0]; + + var deliver_at = command_line.slice(command.length + 1); + + if (message.trim() === '' || deliver_at.trim() === '' || + command_line.slice(command.length, command.length + 1) !== ' ') { + + $("#compose-textarea").attr('disabled', false); + if (command_line.slice(command.length, command.length + 1) !== ' ') { + compose.compose_error(i18n.t('Invalid slash command. Check if you are missing a space after the command.'), $('#compose-textarea')); + } else if (deliver_at.trim() === '') { + compose.compose_error(i18n.t('Please specify time for your reminder.'), $('#compose-textarea')); + } else { + compose.compose_error(i18n.t('Your reminder note is empty!'), $('#compose-textarea')); + } + return; + } + + new_request.content = message; + new_request.deliver_at = deliver_at; + new_request.delivery_type = deferred_message_type.delivery_type; + new_request.tz_guess = moment.tz.guess(); + return new_request; +} + +function do_schedule_message(request, success, error) { + + if (request.type === "private") { + request.to = JSON.stringify(request.to); + } else { + request.to = JSON.stringify([request.to]); + } + + request = patch_request_for_scheduling(request); + + if (request === undefined) { + return; + } + + transmit.send_message(request, success, error); +} + +exports.schedule_message = function (request) { + if (request === undefined) { + request = compose.create_message_object(); + } + + var success = function (data) { + notifications.notify_above_composebox('Scheduled your Message to be delivered at: ' + data.deliver_at); + $("#compose-textarea").attr('disabled', false); + compose.clear_compose_box(); + }; + var error = function (response) { + $("#compose-textarea").attr('disabled', false); + compose.compose_error(response, $('#compose-textarea')); + }; + /* We are adding a disable on compose under this block because we + want slash commands to be blocking in nature. */ + $("#compose-textarea").attr('disabled', true); + + do_schedule_message(request, success, error); +}; + +exports.do_set_reminder_for_message = function (msgid, timestamp) { + var message = current_msg_list.get(msgid); + var link_to_msg = narrow.by_conversation_and_time_uri(message, true); + var command = deferred_message_types.reminders.slash_command; + var reminder_timestamp = timestamp; + var custom_msg = '[this message](' + link_to_msg + ') at ' + reminder_timestamp; + var reminder_msg_content = command + ' ' + reminder_timestamp + '\n' + custom_msg; + var reminder_message = { + type: "private", + content: reminder_msg_content, + sender_id: page_params.user_id, + stream: '', + subject: '', + }; + var recipient = page_params.email; + var emails = util.extract_pm_recipients(recipient); + reminder_message.to = emails; + reminder_message.reply_to = recipient; + reminder_message.private_message_recipient = recipient; + reminder_message.to_user_ids = people.email_list_to_user_ids_string(emails); + + var row = $("[zid='" + msgid + "']"); + + function success() { + row.find(".alert-msg") + .text(i18n.t("Reminder set!")) + .css("display", "block") + .delay(1000).fadeOut(300); + } + + function error() { + row.find(".alert-msg") + .text(i18n.t("Setting reminder failed!")) + .css("display", "block") + .delay(1000).fadeOut(300); + } + + do_schedule_message(reminder_message, success, error); +}; + +return exports; + +}()); + +if (typeof module !== 'undefined') { + module.exports = reminder; +} diff --git a/zproject/settings.py b/zproject/settings.py index 8d40e34294..a9878c66af 100644 --- a/zproject/settings.py +++ b/zproject/settings.py @@ -1045,6 +1045,7 @@ JS_SPECS = { 'js/upload_widget.js', 'js/avatar.js', 'js/realm_icon.js', + 'js/reminder.js', 'js/settings_account.js', 'js/settings_display.js', 'js/settings_notifications.js',