2020-08-01 03:43:15 +02:00
|
|
|
"use strict";
|
|
|
|
|
2020-07-25 02:02:35 +02:00
|
|
|
const _ = require("lodash");
|
|
|
|
|
2020-07-15 01:29:15 +02:00
|
|
|
const render_compose_notification = require("../templates/compose_notification.hbs");
|
|
|
|
const render_notification = require("../templates/notification.hbs");
|
2020-07-24 06:02:07 +02:00
|
|
|
|
2021-02-10 17:01:31 +01:00
|
|
|
const alert_words = require("./alert_words");
|
2021-02-28 00:39:51 +01:00
|
|
|
const channel = require("./channel");
|
2020-10-21 04:07:35 +02:00
|
|
|
const favicon = require("./favicon");
|
2021-02-28 00:41:04 +01:00
|
|
|
const muting = require("./muting");
|
2021-02-28 00:47:56 +01:00
|
|
|
const narrow_state = require("./narrow_state");
|
2020-08-20 21:24:06 +02:00
|
|
|
const people = require("./people");
|
2020-03-28 18:03:43 +01:00
|
|
|
const settings_config = require("./settings_config");
|
2021-02-10 17:10:39 +01:00
|
|
|
const spoilers = require("./spoilers");
|
2021-02-28 00:53:59 +01:00
|
|
|
const stream_data = require("./stream_data");
|
2021-02-10 17:10:22 +01:00
|
|
|
const stream_ui_updates = require("./stream_ui_updates");
|
2019-07-09 21:24:00 +02:00
|
|
|
|
2020-02-06 01:12:42 +01:00
|
|
|
const notice_memory = new Map();
|
2013-09-16 18:22:52 +02:00
|
|
|
|
2020-09-23 02:31:36 +02:00
|
|
|
// When you start Zulip, window_focused should be true, but it might not be the
|
2013-09-16 18:22:52 +02:00
|
|
|
// case after a server-initiated reload.
|
2020-09-23 02:31:36 +02:00
|
|
|
let window_focused = document.hasFocus && document.hasFocus();
|
2013-09-16 18:22:52 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let supports_sound;
|
2012-11-23 23:53:38 +01:00
|
|
|
|
2020-04-27 00:20:20 +02:00
|
|
|
let NotificationAPI;
|
2018-04-04 12:55:34 +02:00
|
|
|
|
|
|
|
exports.set_notification_api = function (n) {
|
2020-04-27 00:20:20 +02:00
|
|
|
NotificationAPI = n;
|
2018-04-04 12:55:34 +02:00
|
|
|
};
|
|
|
|
|
2020-04-27 00:56:59 +02:00
|
|
|
if (window.electron_bridge && window.electron_bridge.new_notification) {
|
|
|
|
class ElectronBridgeNotification extends EventTarget {
|
|
|
|
constructor(title, options) {
|
|
|
|
super();
|
|
|
|
Object.assign(
|
|
|
|
this,
|
|
|
|
window.electron_bridge.new_notification(title, options, (type, eventInit) =>
|
2020-07-02 02:16:03 +02:00
|
|
|
this.dispatchEvent(new Event(type, eventInit)),
|
|
|
|
),
|
2020-04-27 00:56:59 +02:00
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
static get permission() {
|
|
|
|
return Notification.permission;
|
|
|
|
}
|
|
|
|
|
|
|
|
static async requestPermission(callback) {
|
|
|
|
if (callback) {
|
|
|
|
callback(await Promise.resolve(Notification.permission));
|
|
|
|
}
|
|
|
|
return Notification.permission;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
NotificationAPI = ElectronBridgeNotification;
|
|
|
|
} else if (window.Notification) {
|
2020-04-27 00:20:20 +02:00
|
|
|
NotificationAPI = window.Notification;
|
2014-05-29 05:00:45 +02:00
|
|
|
}
|
|
|
|
|
2018-04-04 12:55:34 +02:00
|
|
|
exports.get_notifications = function () {
|
|
|
|
return notice_memory;
|
|
|
|
};
|
|
|
|
|
2018-01-11 21:36:11 +01:00
|
|
|
function get_audio_file_path(audio_element, audio_file_without_extension) {
|
|
|
|
if (audio_element.canPlayType('audio/ogg; codecs="vorbis"')) {
|
|
|
|
return audio_file_without_extension + ".ogg";
|
|
|
|
}
|
|
|
|
|
|
|
|
return audio_file_without_extension + ".mp3";
|
|
|
|
}
|
|
|
|
|
2012-11-26 23:57:31 +01:00
|
|
|
exports.initialize = function () {
|
2020-07-15 00:34:28 +02:00
|
|
|
$(window)
|
2020-07-20 21:26:58 +02:00
|
|
|
.on("focus", () => {
|
2020-09-23 02:31:36 +02:00
|
|
|
window_focused = true;
|
2012-11-27 18:26:36 +01:00
|
|
|
|
2020-07-15 00:34:28 +02:00
|
|
|
for (const notice_mem_entry of notice_memory.values()) {
|
|
|
|
notice_mem_entry.obj.close();
|
|
|
|
}
|
|
|
|
notice_memory.clear();
|
|
|
|
|
|
|
|
// Update many places on the DOM to reflect unread
|
|
|
|
// counts.
|
|
|
|
unread_ops.process_visible();
|
|
|
|
})
|
2020-07-20 21:26:58 +02:00
|
|
|
.on("blur", () => {
|
2020-09-23 02:31:36 +02:00
|
|
|
window_focused = false;
|
2020-07-15 00:34:28 +02:00
|
|
|
});
|
2012-11-23 23:53:38 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const audio = $("<audio>");
|
2013-07-05 20:47:41 +02:00
|
|
|
if (audio[0].canPlayType === undefined) {
|
2013-05-03 21:36:38 +02:00
|
|
|
supports_sound = false;
|
|
|
|
} else {
|
|
|
|
supports_sound = true;
|
2018-01-11 21:36:11 +01:00
|
|
|
|
2013-05-03 21:36:38 +02:00
|
|
|
$("#notifications-area").append(audio);
|
2018-01-11 21:36:11 +01:00
|
|
|
audio.append($("<source>").attr("loop", "yes"));
|
2019-11-02 00:06:25 +01:00
|
|
|
const source = $("#notifications-area audio source");
|
2018-01-11 21:36:11 +01:00
|
|
|
|
2013-05-03 21:36:38 +02:00
|
|
|
if (audio[0].canPlayType('audio/ogg; codecs="vorbis"')) {
|
2018-01-11 21:36:11 +01:00
|
|
|
source.attr("type", "audio/ogg");
|
2013-05-03 21:36:38 +02:00
|
|
|
} else {
|
2018-01-11 21:36:11 +01:00
|
|
|
source.attr("type", "audio/mpeg");
|
2013-05-03 21:36:38 +02:00
|
|
|
}
|
2018-01-11 21:36:11 +01:00
|
|
|
|
2020-07-15 00:34:28 +02:00
|
|
|
const audio_file_without_extension =
|
|
|
|
"/static/audio/notification_sounds/" + page_params.notification_sound;
|
2018-01-11 21:36:11 +01:00
|
|
|
source.attr("src", get_audio_file_path(audio[0], audio_file_without_extension));
|
2013-05-03 21:36:38 +02:00
|
|
|
}
|
2012-11-23 23:53:38 +01:00
|
|
|
};
|
|
|
|
|
2018-01-11 21:36:11 +01:00
|
|
|
function update_notification_sound_source() {
|
|
|
|
// Simplified version of the source creation in `exports.initialize`, for
|
|
|
|
// updating the source instead of creating it for the first time.
|
2019-11-02 00:06:25 +01:00
|
|
|
const audio = $("#notifications-area audio");
|
|
|
|
const source = $("#notifications-area audio source");
|
2020-07-15 00:34:28 +02:00
|
|
|
const audio_file_without_extension =
|
|
|
|
"/static/audio/notification_sounds/" + page_params.notification_sound;
|
2018-01-11 21:36:11 +01:00
|
|
|
source.attr("src", get_audio_file_path(audio[0], audio_file_without_extension));
|
|
|
|
|
|
|
|
// Load it so that it is ready to be played; without this the old sound
|
|
|
|
// is played.
|
|
|
|
$("#notifications-area").find("audio")[0].load();
|
|
|
|
}
|
|
|
|
|
2017-10-19 00:53:26 +02:00
|
|
|
exports.permission_state = function () {
|
2020-04-27 01:09:42 +02:00
|
|
|
if (NotificationAPI === undefined) {
|
2017-10-23 20:32:55 +02:00
|
|
|
// act like notifications are blocked if they do not have access to
|
|
|
|
// the notification API.
|
|
|
|
return "denied";
|
|
|
|
}
|
2020-04-27 01:09:42 +02:00
|
|
|
return NotificationAPI.permission;
|
2017-10-19 00:53:26 +02:00
|
|
|
};
|
|
|
|
|
2020-10-21 04:07:35 +02:00
|
|
|
let unread_count = 0;
|
|
|
|
let pm_count = 0;
|
2014-02-04 00:20:42 +01:00
|
|
|
|
|
|
|
exports.redraw_title = function () {
|
2020-10-21 04:07:35 +02:00
|
|
|
// Update window title to reflect unread messages in current view
|
2020-07-15 00:34:28 +02:00
|
|
|
const new_title =
|
2020-10-21 04:07:35 +02:00
|
|
|
(unread_count ? "(" + unread_count + ") " : "") +
|
2020-07-15 00:34:28 +02:00
|
|
|
narrow.narrow_title +
|
|
|
|
" - " +
|
|
|
|
page_params.realm_name +
|
|
|
|
" - " +
|
|
|
|
"Zulip";
|
2013-03-19 21:53:49 +01:00
|
|
|
|
2020-10-21 04:07:35 +02:00
|
|
|
document.title = new_title;
|
|
|
|
};
|
|
|
|
|
|
|
|
exports.update_unread_counts = function (new_unread_count, new_pm_count) {
|
|
|
|
if (new_unread_count === unread_count && new_pm_count === pm_count) {
|
2013-05-06 22:35:10 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-10-21 04:07:35 +02:00
|
|
|
unread_count = new_unread_count;
|
|
|
|
pm_count = new_pm_count;
|
2013-03-19 21:53:49 +01:00
|
|
|
|
2020-10-21 04:07:35 +02:00
|
|
|
// Indicate the message count in the favicon
|
|
|
|
favicon.update_favicon(unread_count, pm_count);
|
2013-06-02 20:42:03 +02:00
|
|
|
|
2018-02-25 19:51:52 +01:00
|
|
|
// Notify the current desktop app's UI about the new unread count.
|
|
|
|
if (window.electron_bridge !== undefined) {
|
2020-10-21 04:07:35 +02:00
|
|
|
window.electron_bridge.send_event("total_unread_count", unread_count);
|
2013-06-28 20:51:28 +02:00
|
|
|
}
|
|
|
|
|
2018-05-16 00:50:52 +02:00
|
|
|
// TODO: Add a `window.electron_bridge.updatePMCount(new_pm_count);` call?
|
2020-10-21 04:07:35 +02:00
|
|
|
|
|
|
|
exports.redraw_title();
|
2013-06-19 00:00:40 +02:00
|
|
|
};
|
|
|
|
|
2020-09-23 02:31:36 +02:00
|
|
|
exports.is_window_focused = function () {
|
|
|
|
return window_focused;
|
2013-03-04 23:44:07 +01:00
|
|
|
};
|
|
|
|
|
2016-12-23 15:33:08 +01:00
|
|
|
function in_browser_notify(message, title, content, raw_operators, opts) {
|
2020-07-15 00:34:28 +02:00
|
|
|
const notification_html = $(
|
|
|
|
render_notification({
|
|
|
|
gravatar_url: people.small_avatar_url(message),
|
2020-07-20 22:18:43 +02:00
|
|
|
title,
|
|
|
|
content,
|
2020-07-15 00:34:28 +02:00
|
|
|
message_id: message.id,
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
$(".top-right")
|
|
|
|
.notify({
|
|
|
|
message: {
|
|
|
|
html: notification_html,
|
|
|
|
},
|
|
|
|
fadeOut: {
|
|
|
|
enabled: true,
|
|
|
|
delay: 4000,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
.show();
|
|
|
|
|
2021-02-03 23:23:32 +01:00
|
|
|
$(`.notification[data-message-id='${CSS.escape(message.id)}']`)
|
2020-07-15 00:34:28 +02:00
|
|
|
.expectOne()
|
|
|
|
.data("narrow", {
|
2020-07-20 22:18:43 +02:00
|
|
|
raw_operators,
|
2020-07-15 00:34:28 +02:00
|
|
|
opts_notif: opts,
|
|
|
|
});
|
2013-06-27 22:15:00 +02:00
|
|
|
}
|
|
|
|
|
2013-11-13 19:40:02 +01:00
|
|
|
exports.notify_above_composebox = function (note, link_class, link_msg_id, link_text) {
|
2020-07-15 00:34:28 +02:00
|
|
|
const notification_html = $(
|
|
|
|
render_compose_notification({
|
2020-07-20 22:18:43 +02:00
|
|
|
note,
|
|
|
|
link_class,
|
|
|
|
link_msg_id,
|
|
|
|
link_text,
|
2020-07-15 00:34:28 +02:00
|
|
|
}),
|
|
|
|
);
|
2013-11-13 19:40:02 +01:00
|
|
|
exports.clear_compose_notifications();
|
2020-07-15 01:29:15 +02:00
|
|
|
$("#out-of-view-notification").append(notification_html);
|
|
|
|
$("#out-of-view-notification").show();
|
2013-10-09 22:42:15 +02:00
|
|
|
};
|
|
|
|
|
2019-07-16 02:52:55 +02:00
|
|
|
if (window.electron_bridge !== undefined) {
|
|
|
|
// The code below is for sending a message received from notification reply which
|
2020-03-28 01:25:56 +01:00
|
|
|
// is often referred to as inline reply feature. This is done so desktop app doesn't
|
2019-07-16 02:52:55 +02:00
|
|
|
// have to depend on channel.post for setting crsf_token and narrow.by_topic
|
|
|
|
// to narrow to the message being sent.
|
2020-04-26 23:13:25 +02:00
|
|
|
if (window.electron_bridge.set_send_notification_reply_message_supported !== undefined) {
|
|
|
|
window.electron_bridge.set_send_notification_reply_message_supported(true);
|
|
|
|
}
|
2020-07-15 01:29:15 +02:00
|
|
|
window.electron_bridge.on_event("send_notification_reply_message", (message_id, reply) => {
|
2019-11-02 00:06:25 +01:00
|
|
|
const message = message_store.get(message_id);
|
|
|
|
const data = {
|
2019-07-16 02:52:55 +02:00
|
|
|
type: message.type,
|
|
|
|
content: reply,
|
2020-07-15 01:29:15 +02:00
|
|
|
to: message.type === "private" ? message.reply_to : message.stream,
|
2020-02-19 00:04:12 +01:00
|
|
|
topic: message.topic,
|
2019-07-16 02:52:55 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
function success() {
|
2020-07-15 01:29:15 +02:00
|
|
|
if (message.type === "stream") {
|
|
|
|
narrow.by_topic(message_id, {trigger: "desktop_notification_reply"});
|
2019-07-16 02:52:55 +02:00
|
|
|
} else {
|
2020-07-15 01:29:15 +02:00
|
|
|
narrow.by_recipient(message_id, {trigger: "desktop_notification_reply"});
|
2019-07-16 02:52:55 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function error(error) {
|
2020-07-15 01:29:15 +02:00
|
|
|
window.electron_bridge.send_event("send_notification_reply_message_failed", {
|
2020-07-20 22:18:43 +02:00
|
|
|
data,
|
|
|
|
message_id,
|
|
|
|
error,
|
2019-07-16 02:52:55 +02:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
channel.post({
|
2020-07-15 01:29:15 +02:00
|
|
|
url: "/json/messages",
|
2020-07-20 22:18:43 +02:00
|
|
|
data,
|
|
|
|
success,
|
|
|
|
error,
|
2019-07-16 02:52:55 +02:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2013-06-19 01:41:27 +02:00
|
|
|
function process_notification(notification) {
|
2019-11-02 00:06:25 +01:00
|
|
|
let i;
|
|
|
|
let notification_object;
|
|
|
|
let key;
|
|
|
|
let content;
|
|
|
|
let other_recipients;
|
|
|
|
const message = notification.message;
|
|
|
|
let title = message.sender_full_name;
|
|
|
|
let msg_count = 1;
|
|
|
|
let notification_source;
|
|
|
|
let raw_operators = [];
|
|
|
|
const opts = {trigger: "notification click"};
|
2013-07-22 22:21:34 +02:00
|
|
|
// Convert the content to plain text, replacing emoji with their alt text
|
2020-07-15 01:29:15 +02:00
|
|
|
content = $("<div/>").html(message.content);
|
2013-07-23 00:25:25 +02:00
|
|
|
ui.replace_emoji_with_text(content);
|
2020-07-15 02:08:01 +02:00
|
|
|
spoilers.hide_spoilers_in_notification(content);
|
2013-07-22 22:21:34 +02:00
|
|
|
content = content.text();
|
|
|
|
|
2020-02-19 00:04:12 +01:00
|
|
|
const topic = message.topic;
|
2018-12-22 23:15:04 +01:00
|
|
|
|
2014-03-10 16:26:39 +01:00
|
|
|
if (message.is_me_message) {
|
|
|
|
content = message.sender_full_name + content.slice(3);
|
|
|
|
}
|
|
|
|
|
2020-04-27 00:02:36 +02:00
|
|
|
if (message.type === "private" || message.type === "test-notification") {
|
2020-07-15 00:34:28 +02:00
|
|
|
if (
|
|
|
|
page_params.pm_content_in_desktop_notifications !== undefined &&
|
|
|
|
!page_params.pm_content_in_desktop_notifications
|
|
|
|
) {
|
2016-12-07 17:29:12 +01:00
|
|
|
content = "New private message from " + message.sender_full_name;
|
|
|
|
}
|
2013-01-09 23:49:54 +01:00
|
|
|
key = message.display_reply_to;
|
|
|
|
other_recipients = message.display_reply_to;
|
|
|
|
// Remove the sender from the list of other recipients
|
|
|
|
other_recipients = other_recipients.replace(", " + message.sender_full_name, "");
|
|
|
|
other_recipients = other_recipients.replace(message.sender_full_name + ", ", "");
|
2020-07-15 01:29:15 +02:00
|
|
|
notification_source = "pm";
|
2013-01-09 23:49:54 +01:00
|
|
|
} else {
|
2020-07-15 00:34:28 +02:00
|
|
|
key = message.sender_full_name + " to " + message.stream + " > " + topic;
|
2014-02-05 17:33:07 +01:00
|
|
|
if (message.mentioned) {
|
2020-07-15 01:29:15 +02:00
|
|
|
notification_source = "mention";
|
2014-02-05 17:33:07 +01:00
|
|
|
} else if (message.alerted) {
|
2020-07-15 01:29:15 +02:00
|
|
|
notification_source = "alert";
|
2014-02-05 17:33:07 +01:00
|
|
|
} else {
|
2020-07-15 01:29:15 +02:00
|
|
|
notification_source = "stream";
|
2014-02-05 17:33:07 +01:00
|
|
|
}
|
2013-01-09 23:49:54 +01:00
|
|
|
}
|
2018-05-16 00:50:52 +02:00
|
|
|
blueslip.debug("Desktop notification from source " + notification_source);
|
2012-11-23 23:53:38 +01:00
|
|
|
|
|
|
|
if (content.length > 150) {
|
|
|
|
// Truncate content at a word boundary
|
2016-11-30 19:05:04 +01:00
|
|
|
for (i = 150; i > 0; i -= 1) {
|
2020-07-15 01:29:15 +02:00
|
|
|
if (content[i] === " ") {
|
2012-11-23 23:53:38 +01:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
2020-10-07 09:41:22 +02:00
|
|
|
content = content.slice(0, i);
|
2012-11-23 23:53:38 +01:00
|
|
|
content += " [...]";
|
|
|
|
}
|
|
|
|
|
2020-02-06 01:12:42 +01:00
|
|
|
if (notice_memory.has(key)) {
|
|
|
|
msg_count = notice_memory.get(key).msg_count + 1;
|
2012-11-23 23:53:38 +01:00
|
|
|
title = msg_count + " messages from " + title;
|
2020-02-06 01:12:42 +01:00
|
|
|
notification_object = notice_memory.get(key).obj;
|
2020-04-27 00:27:03 +02:00
|
|
|
notification_object.close();
|
2012-11-23 23:53:38 +01:00
|
|
|
}
|
|
|
|
|
2017-01-03 01:44:49 +01:00
|
|
|
if (message.type === "private") {
|
|
|
|
if (message.display_recipient.length > 2) {
|
|
|
|
// If the message has too many recipients to list them all...
|
|
|
|
if (content.length + title.length + other_recipients.length > 230) {
|
|
|
|
// Then count how many people are in the conversation and summarize
|
|
|
|
// by saying the conversation is with "you and [number] other people"
|
2020-07-15 00:34:28 +02:00
|
|
|
other_recipients = other_recipients.replace(/[^,]/g, "").length + " other people";
|
2017-01-03 01:44:49 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
title += " (to you and " + other_recipients + ")";
|
|
|
|
} else {
|
|
|
|
title += " (to you)";
|
2012-11-23 23:53:38 +01:00
|
|
|
}
|
2017-01-03 01:44:49 +01:00
|
|
|
|
2016-12-23 15:33:08 +01:00
|
|
|
raw_operators = [{operand: message.reply_to, operator: "pm-with"}];
|
2012-11-23 23:53:38 +01:00
|
|
|
}
|
2017-01-03 01:44:49 +01:00
|
|
|
|
2013-01-09 23:49:54 +01:00
|
|
|
if (message.type === "stream") {
|
2018-12-22 23:15:04 +01:00
|
|
|
title += " (to " + message.stream + " > " + topic + ")";
|
2018-05-06 21:43:17 +02:00
|
|
|
raw_operators = [
|
|
|
|
{operator: "stream", operand: message.stream},
|
2018-12-22 23:15:04 +01:00
|
|
|
{operator: "topic", operand: topic},
|
2018-05-06 21:43:17 +02:00
|
|
|
];
|
2013-01-09 23:49:54 +01:00
|
|
|
}
|
2012-11-23 23:53:38 +01:00
|
|
|
|
2020-04-27 01:04:43 +02:00
|
|
|
if (notification.desktop_notify) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const icon_url = people.small_avatar_url(message);
|
2020-04-27 00:20:20 +02:00
|
|
|
notification_object = new NotificationAPI(title, {
|
|
|
|
icon: icon_url,
|
|
|
|
body: content,
|
|
|
|
tag: message.id,
|
|
|
|
});
|
2020-02-06 01:12:42 +01:00
|
|
|
notice_memory.set(key, {
|
|
|
|
obj: notification_object,
|
2020-07-20 22:18:43 +02:00
|
|
|
msg_count,
|
2017-01-12 00:17:43 +01:00
|
|
|
message_id: message.id,
|
2020-02-06 01:12:42 +01:00
|
|
|
});
|
2020-06-18 21:47:01 +02:00
|
|
|
|
|
|
|
if (_.isFunction(notification_object.addEventListener)) {
|
|
|
|
// Sadly, some third-party Electron apps like Franz/Ferdi
|
|
|
|
// misimplement the Notification API not inheriting from
|
|
|
|
// EventTarget. This results in addEventListener being
|
|
|
|
// unavailable for them.
|
|
|
|
notification_object.addEventListener("click", () => {
|
|
|
|
notification_object.close();
|
|
|
|
if (message.type !== "test-notification") {
|
2020-07-15 01:29:15 +02:00
|
|
|
narrow.by_topic(message.id, {trigger: "notification"});
|
2020-06-18 21:47:01 +02:00
|
|
|
}
|
2020-07-24 23:42:18 +02:00
|
|
|
window.focus();
|
2020-06-18 21:47:01 +02:00
|
|
|
});
|
|
|
|
notification_object.addEventListener("close", () => {
|
|
|
|
notice_memory.delete(key);
|
|
|
|
});
|
|
|
|
}
|
2019-05-16 06:27:37 +02:00
|
|
|
} else {
|
2016-12-23 15:33:08 +01:00
|
|
|
in_browser_notify(message, title, content, raw_operators, opts);
|
2013-05-31 22:25:39 +02:00
|
|
|
}
|
2012-11-23 23:53:38 +01:00
|
|
|
}
|
|
|
|
|
2018-04-04 12:55:34 +02:00
|
|
|
exports.process_notification = process_notification;
|
|
|
|
|
2013-08-29 22:58:00 +02:00
|
|
|
exports.close_notification = function (message) {
|
2020-02-06 01:12:42 +01:00
|
|
|
for (const [key, notice_mem_entry] of notice_memory) {
|
|
|
|
if (notice_mem_entry.message_id === message.id) {
|
2020-04-27 00:27:03 +02:00
|
|
|
notice_mem_entry.obj.close();
|
2020-02-06 01:12:42 +01:00
|
|
|
notice_memory.delete(key);
|
2018-05-06 21:43:17 +02:00
|
|
|
}
|
2020-02-06 01:12:42 +01:00
|
|
|
}
|
2013-08-29 22:58:00 +02:00
|
|
|
};
|
|
|
|
|
2018-02-12 22:15:57 +01:00
|
|
|
exports.message_is_notifiable = function (message) {
|
2014-02-05 22:56:30 +01:00
|
|
|
// Independent of the user's notification settings, are there
|
|
|
|
// properties of the message that unconditionally mean we
|
|
|
|
// shouldn't notify about it.
|
2013-10-18 19:28:29 +02:00
|
|
|
|
|
|
|
if (message.sent_by_me) {
|
|
|
|
return false;
|
|
|
|
}
|
2014-01-08 22:02:02 +01:00
|
|
|
|
|
|
|
// If a message is edited multiple times, we want to err on the side of
|
|
|
|
// not spamming notifications.
|
|
|
|
if (message.notification_sent) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2017-08-25 08:56:10 +02:00
|
|
|
// @-<username> mentions take precedence over muted-ness. Note
|
|
|
|
// that @all mentions are still suppressed by muting.
|
|
|
|
if (message.mentioned_me_directly) {
|
2013-12-09 23:13:33 +01:00
|
|
|
return true;
|
|
|
|
}
|
2017-08-25 08:56:10 +02:00
|
|
|
|
|
|
|
// Messages to muted streams that don't mention us specifically
|
|
|
|
// are not notifiable.
|
2020-07-15 00:34:28 +02:00
|
|
|
if (message.type === "stream" && stream_data.is_muted(message.stream_id)) {
|
2013-10-18 19:47:16 +02:00
|
|
|
return false;
|
|
|
|
}
|
2017-08-25 08:56:10 +02:00
|
|
|
|
2020-07-15 00:34:28 +02:00
|
|
|
if (message.type === "stream" && muting.is_topic_muted(message.stream_id, message.topic)) {
|
2013-10-18 19:47:16 +02:00
|
|
|
return false;
|
|
|
|
}
|
2013-10-18 19:28:29 +02:00
|
|
|
|
2014-02-05 22:56:30 +01:00
|
|
|
// Everything else is on the table; next filter based on notification
|
|
|
|
// settings.
|
|
|
|
return true;
|
2018-02-12 22:15:57 +01:00
|
|
|
};
|
2014-02-05 22:56:30 +01:00
|
|
|
|
2019-12-10 21:54:08 +01:00
|
|
|
exports.should_send_desktop_notification = function (message) {
|
2020-04-27 00:02:36 +02:00
|
|
|
// Always notify for testing notifications.
|
|
|
|
if (message.type === "test-notification") {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-12-10 22:03:40 +01:00
|
|
|
// For streams, send if desktop notifications are enabled for all
|
|
|
|
// message on this stream.
|
2020-07-15 00:34:28 +02:00
|
|
|
if (
|
|
|
|
message.type === "stream" &&
|
|
|
|
stream_data.receives_notifications(message.stream_id, "desktop_notifications")
|
|
|
|
) {
|
2014-02-05 22:56:30 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-12-10 22:03:40 +01:00
|
|
|
// enable_desktop_notifications determines whether we pop up a
|
|
|
|
// notification for PMs/mentions/alerts
|
|
|
|
if (!page_params.enable_desktop_notifications) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// And then we need to check if the message is a PM, mention,
|
|
|
|
// wildcard mention with wildcard_mentions_notify, or alert.
|
|
|
|
if (message.type === "private") {
|
2014-02-05 22:56:30 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-12-10 22:03:40 +01:00
|
|
|
if (alert_words.notifies(message)) {
|
2014-02-05 22:56:30 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-12-10 22:03:40 +01:00
|
|
|
if (message.mentioned_me_directly) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// wildcard mentions
|
2020-07-15 00:34:28 +02:00
|
|
|
if (
|
|
|
|
message.mentioned &&
|
|
|
|
stream_data.receives_notifications(message.stream_id, "wildcard_mentions_notify")
|
|
|
|
) {
|
2013-10-18 19:28:29 +02:00
|
|
|
return true;
|
|
|
|
}
|
2014-02-05 22:56:30 +01:00
|
|
|
|
|
|
|
return false;
|
2019-12-10 21:54:08 +01:00
|
|
|
};
|
2014-02-05 22:56:30 +01:00
|
|
|
|
2019-12-10 21:54:08 +01:00
|
|
|
exports.should_send_audible_notification = function (message) {
|
2019-12-10 22:03:40 +01:00
|
|
|
// For streams, ding if sounds are enabled for all messages on
|
|
|
|
// this stream.
|
2020-07-15 00:34:28 +02:00
|
|
|
if (
|
|
|
|
message.type === "stream" &&
|
|
|
|
stream_data.receives_notifications(message.stream_id, "audible_notifications")
|
|
|
|
) {
|
2014-02-05 22:56:30 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-12-10 22:03:40 +01:00
|
|
|
// enable_sounds determines whether we ding for PMs/mentions/alerts
|
|
|
|
if (!page_params.enable_sounds) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// And then we need to check if the message is a PM, mention,
|
|
|
|
// wildcard mention with wildcard_mentions_notify, or alert.
|
2020-04-27 00:02:36 +02:00
|
|
|
if (message.type === "private" || message.type === "test-notification") {
|
2019-12-10 22:03:40 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (alert_words.notifies(message)) {
|
2014-02-05 22:56:30 +01:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2019-12-10 22:03:40 +01:00
|
|
|
if (message.mentioned_me_directly) {
|
2013-10-18 19:28:29 +02:00
|
|
|
return true;
|
|
|
|
}
|
2014-02-05 22:56:30 +01:00
|
|
|
|
2019-12-10 22:03:40 +01:00
|
|
|
// wildcard mentions
|
2020-07-15 00:34:28 +02:00
|
|
|
if (
|
|
|
|
message.mentioned &&
|
|
|
|
stream_data.receives_notifications(message.stream_id, "wildcard_mentions_notify")
|
|
|
|
) {
|
2013-10-18 19:28:29 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2019-12-10 21:54:08 +01:00
|
|
|
};
|
2013-05-03 21:49:01 +02:00
|
|
|
|
2017-09-23 11:27:18 +02:00
|
|
|
exports.granted_desktop_notifications_permission = function () {
|
2020-07-15 00:34:28 +02:00
|
|
|
return NotificationAPI && NotificationAPI.permission === "granted";
|
2017-09-23 11:27:18 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
exports.request_desktop_notifications_permission = function () {
|
2020-04-27 00:20:20 +02:00
|
|
|
if (NotificationAPI) {
|
2020-09-24 07:50:36 +02:00
|
|
|
NotificationAPI.requestPermission();
|
2017-09-23 11:27:18 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2012-11-23 23:53:38 +01:00
|
|
|
exports.received_messages = function (messages) {
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
for (const message of messages) {
|
2018-02-12 22:15:57 +01:00
|
|
|
if (!exports.message_is_notifiable(message)) {
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
continue;
|
2013-08-01 17:47:48 +02:00
|
|
|
}
|
2017-12-27 13:17:58 +01:00
|
|
|
if (!unread.message_unread(message)) {
|
2018-01-31 01:17:16 +01:00
|
|
|
// The message is already read; Zulip is currently in focus.
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
continue;
|
2013-08-01 17:47:48 +02:00
|
|
|
}
|
2013-07-02 20:33:00 +02:00
|
|
|
|
2014-01-08 22:02:02 +01:00
|
|
|
message.notification_sent = true;
|
|
|
|
|
2019-12-10 21:54:08 +01:00
|
|
|
if (exports.should_send_desktop_notification(message)) {
|
2019-05-16 06:27:37 +02:00
|
|
|
process_notification({
|
2020-07-20 22:18:43 +02:00
|
|
|
message,
|
2019-05-16 06:27:37 +02:00
|
|
|
desktop_notify: exports.granted_desktop_notifications_permission(),
|
|
|
|
});
|
2013-06-14 16:46:37 +02:00
|
|
|
}
|
2019-12-10 21:54:08 +01:00
|
|
|
if (exports.should_send_audible_notification(message) && supports_sound) {
|
2018-05-16 00:50:52 +02:00
|
|
|
$("#notifications-area").find("audio")[0].play();
|
2012-11-23 23:53:38 +01:00
|
|
|
}
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
}
|
2012-11-23 23:53:38 +01:00
|
|
|
};
|
|
|
|
|
2020-04-27 00:02:36 +02:00
|
|
|
exports.send_test_notification = function (content) {
|
2020-07-15 00:34:28 +02:00
|
|
|
exports.received_messages([
|
|
|
|
{
|
|
|
|
id: Math.random(),
|
|
|
|
type: "test-notification",
|
|
|
|
sender_email: "notification-bot@zulip.com",
|
|
|
|
sender_full_name: "Notification Bot",
|
|
|
|
display_reply_to: "Notification Bot",
|
|
|
|
content,
|
|
|
|
unread: true,
|
|
|
|
},
|
|
|
|
]);
|
2020-04-27 00:02:36 +02:00
|
|
|
};
|
|
|
|
|
2020-11-29 14:13:36 +01:00
|
|
|
// Note that this returns values that are not HTML-escaped, for use in
|
|
|
|
// handlebars templates that will do further escaping.
|
2013-11-13 19:40:02 +01:00
|
|
|
function get_message_header(message) {
|
|
|
|
if (message.type === "stream") {
|
2020-02-19 00:04:12 +01:00
|
|
|
return message.stream + " > " + message.topic;
|
2013-11-13 19:40:02 +01:00
|
|
|
}
|
|
|
|
if (message.display_recipient.length > 2) {
|
2020-11-29 14:13:36 +01:00
|
|
|
return i18n.t("group private messages with __- recipient__", {
|
2020-07-15 00:34:28 +02:00
|
|
|
recipient: message.display_reply_to,
|
|
|
|
});
|
2013-11-13 19:40:02 +01:00
|
|
|
}
|
2017-01-19 20:18:03 +01:00
|
|
|
if (people.is_current_user(message.reply_to)) {
|
2018-12-16 20:14:09 +01:00
|
|
|
return i18n.t("private messages with yourself");
|
2013-11-13 19:40:02 +01:00
|
|
|
}
|
2020-11-29 14:13:36 +01:00
|
|
|
return i18n.t("private messages with __- recipient__", {recipient: message.display_reply_to});
|
2013-11-13 19:40:02 +01:00
|
|
|
}
|
|
|
|
|
2017-07-18 20:33:51 +02:00
|
|
|
exports.get_local_notify_mix_reason = function (message) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const row = current_msg_list.get_row(message.id);
|
2017-07-18 20:33:51 +02:00
|
|
|
if (row.length > 0) {
|
|
|
|
// If our message is in the current message list, we do
|
|
|
|
// not have a mix, so we are happy.
|
2020-09-24 07:50:36 +02:00
|
|
|
return undefined;
|
2017-07-18 20:33:51 +02:00
|
|
|
}
|
|
|
|
|
2020-02-19 00:04:12 +01:00
|
|
|
if (message.type === "stream" && muting.is_topic_muted(message.stream_id, message.topic)) {
|
2018-12-16 20:14:09 +01:00
|
|
|
return i18n.t("Sent! Your message was sent to a topic you have muted.");
|
2017-07-18 20:33:51 +02:00
|
|
|
}
|
|
|
|
|
2019-05-21 09:33:21 +02:00
|
|
|
if (message.type === "stream" && stream_data.is_muted(message.stream_id)) {
|
2018-12-16 20:14:09 +01:00
|
|
|
return i18n.t("Sent! Your message was sent to a stream you have muted.");
|
2017-07-18 20:33:51 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// offscreen because it is outside narrow
|
|
|
|
// we can only look for these on non-search (can_apply_locally) messages
|
|
|
|
// see also: exports.notify_messages_outside_current_search
|
2020-06-23 16:50:21 +02:00
|
|
|
const current_filter = narrow_state.filter();
|
2020-07-15 00:34:28 +02:00
|
|
|
if (
|
|
|
|
current_filter &&
|
|
|
|
current_filter.can_apply_locally() &&
|
|
|
|
!current_filter.predicate()(message)
|
|
|
|
) {
|
2020-06-23 16:50:21 +02:00
|
|
|
return i18n.t("Sent! Your message is outside your current narrow.");
|
|
|
|
}
|
2020-09-24 07:50:36 +02:00
|
|
|
|
|
|
|
return undefined;
|
2017-07-18 20:33:51 +02:00
|
|
|
};
|
|
|
|
|
message scrolling: Fix "Scroll down to view" warning.
We recently added a feature to warn users that they
may need to scroll down to view messages that they
just sent, but it was broken due to various complexities
in the rendering code path.
Now we compute it a bit more rigorously.
It requires us to pass some info about rendering up
and down the stack, which is why it's kind of a long
commit, but the bulk of the logic is in these JS files:
* message_list_view.js
* notifications.js
I choose to pass structs around instead of booleans,
because I anticipate we may eventually add more metadata
about rendering to it, plus bools are just kinda brittle.
(The exceptions are that `_maybe_autoscroll`, which
is at the bottom of the stack, just passes back a simple
boolean, and `notify_local_mixes`, also at the bottom
of the stack, just accepts a simple boolean.)
This errs on the side of warning the user, even if the
new message is partially visible.
Fixes #11138
2019-01-07 21:00:03 +01:00
|
|
|
exports.notify_local_mixes = function (messages, need_user_to_scroll) {
|
2017-07-18 20:03:14 +02:00
|
|
|
/*
|
2019-02-12 03:23:56 +01:00
|
|
|
This code should only be called when we are displaying
|
|
|
|
messages sent by current client. It notifies users that
|
|
|
|
their messages aren't actually in the view that they
|
|
|
|
composed to.
|
2017-07-18 20:03:14 +02:00
|
|
|
|
|
|
|
This code is called after we insert messages into our
|
2019-02-12 03:23:56 +01:00
|
|
|
message list widgets. All of the conditions here are
|
2017-07-18 20:03:14 +02:00
|
|
|
checkable locally, so we may want to execute this code
|
|
|
|
earlier in the codepath at some point and possibly punt
|
|
|
|
on local rendering.
|
2019-02-12 03:25:26 +01:00
|
|
|
|
|
|
|
Possible cleanup: Arguably, we should call this function
|
|
|
|
unconditionally and just check if message.local_id is in
|
|
|
|
sent_messages.messages here.
|
2017-07-18 20:03:14 +02:00
|
|
|
*/
|
2017-07-18 17:47:08 +02:00
|
|
|
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
for (const message of messages) {
|
2017-07-18 17:47:08 +02:00
|
|
|
if (!people.is_my_user_id(message.sender_id)) {
|
2019-02-12 03:25:26 +01:00
|
|
|
// This can happen if the client is offline for a while
|
|
|
|
// around the time this client sends a message; see the
|
|
|
|
// caller of message_events.insert_new_messages.
|
2020-07-15 00:34:28 +02:00
|
|
|
blueslip.info(
|
|
|
|
"Slightly unexpected: A message not sent by us batches with those that were.",
|
|
|
|
);
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
continue;
|
2013-10-09 22:42:15 +02:00
|
|
|
}
|
2013-11-13 19:40:02 +01:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
let reason = exports.get_local_notify_mix_reason(message);
|
2017-07-18 20:33:51 +02:00
|
|
|
|
|
|
|
if (!reason) {
|
message scrolling: Fix "Scroll down to view" warning.
We recently added a feature to warn users that they
may need to scroll down to view messages that they
just sent, but it was broken due to various complexities
in the rendering code path.
Now we compute it a bit more rigorously.
It requires us to pass some info about rendering up
and down the stack, which is why it's kind of a long
commit, but the bulk of the logic is in these JS files:
* message_list_view.js
* notifications.js
I choose to pass structs around instead of booleans,
because I anticipate we may eventually add more metadata
about rendering to it, plus bools are just kinda brittle.
(The exceptions are that `_maybe_autoscroll`, which
is at the bottom of the stack, just passes back a simple
boolean, and `notify_local_mixes`, also at the bottom
of the stack, just accepts a simple boolean.)
This errs on the side of warning the user, even if the
new message is partially visible.
Fixes #11138
2019-01-07 21:00:03 +01:00
|
|
|
if (need_user_to_scroll) {
|
|
|
|
reason = i18n.t("Sent! Scroll down to view your message.");
|
2018-11-30 15:06:43 +01:00
|
|
|
exports.notify_above_composebox(reason, "", null, "");
|
2020-07-02 01:45:54 +02:00
|
|
|
setTimeout(() => {
|
2020-07-15 01:29:15 +02:00
|
|
|
$("#out-of-view-notification").hide();
|
2018-11-30 15:06:43 +01:00
|
|
|
}, 3000);
|
|
|
|
}
|
|
|
|
|
message scrolling: Fix "Scroll down to view" warning.
We recently added a feature to warn users that they
may need to scroll down to view messages that they
just sent, but it was broken due to various complexities
in the rendering code path.
Now we compute it a bit more rigorously.
It requires us to pass some info about rendering up
and down the stack, which is why it's kind of a long
commit, but the bulk of the logic is in these JS files:
* message_list_view.js
* notifications.js
I choose to pass structs around instead of booleans,
because I anticipate we may eventually add more metadata
about rendering to it, plus bools are just kinda brittle.
(The exceptions are that `_maybe_autoscroll`, which
is at the bottom of the stack, just passes back a simple
boolean, and `notify_local_mixes`, also at the bottom
of the stack, just accepts a simple boolean.)
This errs on the side of warning the user, even if the
new message is partially visible.
Fixes #11138
2019-01-07 21:00:03 +01:00
|
|
|
// This is the HAPPY PATH--for most messages we do nothing
|
|
|
|
// other than maybe sending the above message.
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
continue;
|
2013-10-09 22:42:15 +02:00
|
|
|
}
|
2017-07-18 20:33:51 +02:00
|
|
|
|
2019-11-02 00:06:25 +01:00
|
|
|
const link_msg_id = message.id;
|
|
|
|
const link_class = "compose_notification_narrow_by_topic";
|
2020-07-15 00:34:28 +02:00
|
|
|
const link_text = i18n.t("Narrow to __- message_recipient__", {
|
|
|
|
message_recipient: get_message_header(message),
|
|
|
|
});
|
2019-01-08 00:34:03 +01:00
|
|
|
|
2017-07-18 20:33:51 +02:00
|
|
|
exports.notify_above_composebox(reason, link_class, link_msg_id, link_text);
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
}
|
2013-10-09 22:42:15 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
// for callback when we have to check with the server if a message should be in
|
|
|
|
// the current_msg_list (!can_apply_locally; a.k.a. "a search").
|
|
|
|
exports.notify_messages_outside_current_search = function (messages) {
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
for (const message of messages) {
|
2017-01-19 20:18:03 +01:00
|
|
|
if (!people.is_current_user(message.sender_email)) {
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
continue;
|
2013-10-09 22:42:15 +02:00
|
|
|
}
|
2020-07-15 00:34:28 +02:00
|
|
|
const link_text = i18n.t("Narrow to __- message_recipient__", {
|
|
|
|
message_recipient: get_message_header(message),
|
|
|
|
});
|
|
|
|
exports.notify_above_composebox(
|
|
|
|
i18n.t("Sent! Your recent message is outside the current search."),
|
|
|
|
"compose_notification_narrow_by_topic",
|
|
|
|
message.id,
|
|
|
|
link_text,
|
|
|
|
);
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
}
|
2013-10-09 22:42:15 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
exports.clear_compose_notifications = function () {
|
2020-07-15 01:29:15 +02:00
|
|
|
$("#out-of-view-notification").empty();
|
|
|
|
$("#out-of-view-notification").stop(true, true);
|
|
|
|
$("#out-of-view-notification").hide();
|
2013-10-09 22:42:15 +02:00
|
|
|
};
|
|
|
|
|
2017-07-19 14:39:28 +02:00
|
|
|
exports.reify_message_id = function (opts) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const old_id = opts.old_id;
|
|
|
|
const new_id = opts.new_id;
|
2017-07-19 14:39:28 +02:00
|
|
|
|
|
|
|
// If a message ID that we're currently storing (as a link) has changed,
|
|
|
|
// update that link as well
|
2020-07-15 01:29:15 +02:00
|
|
|
for (const e of $("#out-of-view-notification a")) {
|
2019-11-02 00:06:25 +01:00
|
|
|
const elem = $(e);
|
2020-07-15 01:29:15 +02:00
|
|
|
const message_id = elem.data("message-id");
|
2017-07-19 14:39:28 +02:00
|
|
|
|
2019-08-28 17:46:10 +02:00
|
|
|
if (message_id === old_id) {
|
2020-07-15 01:29:15 +02:00
|
|
|
elem.data("message-id", new_id);
|
2017-07-19 14:39:28 +02:00
|
|
|
}
|
js: Automatically convert _.each to for…of.
This commit was automatically generated by the following script,
followed by lint --fix and a few small manual lint-related cleanups.
import * as babelParser from "recast/parsers/babel";
import * as recast from "recast";
import * as tsParser from "recast/parsers/typescript";
import { builders as b, namedTypes as n } from "ast-types";
import { Context } from "ast-types/lib/path-visitor";
import K from "ast-types/gen/kinds";
import { NodePath } from "ast-types/lib/node-path";
import assert from "assert";
import fs from "fs";
import path from "path";
import process from "process";
const checkExpression = (node: n.Node): node is K.ExpressionKind =>
n.Expression.check(node);
const checkStatement = (node: n.Node): node is K.StatementKind =>
n.Statement.check(node);
for (const file of process.argv.slice(2)) {
console.log("Parsing", file);
const ast = recast.parse(fs.readFileSync(file, { encoding: "utf8" }), {
parser: path.extname(file) === ".ts" ? tsParser : babelParser,
});
let changed = false;
let inLoop = false;
let replaceReturn = false;
const visitLoop = (...args: string[]) =>
function(this: Context, path: NodePath) {
for (const arg of args) {
this.visit(path.get(arg));
}
const old = { inLoop };
inLoop = true;
this.visit(path.get("body"));
inLoop = old.inLoop;
return false;
};
recast.visit(ast, {
visitDoWhileStatement: visitLoop("test"),
visitExpressionStatement(path) {
const { expression, comments } = path.node;
let valueOnly;
if (
n.CallExpression.check(expression) &&
n.MemberExpression.check(expression.callee) &&
!expression.callee.computed &&
n.Identifier.check(expression.callee.object) &&
expression.callee.object.name === "_" &&
n.Identifier.check(expression.callee.property) &&
["each", "forEach"].includes(expression.callee.property.name) &&
[2, 3].includes(expression.arguments.length) &&
checkExpression(expression.arguments[0]) &&
(n.FunctionExpression.check(expression.arguments[1]) ||
n.ArrowFunctionExpression.check(expression.arguments[1])) &&
[1, 2].includes(expression.arguments[1].params.length) &&
n.Identifier.check(expression.arguments[1].params[0]) &&
((valueOnly = expression.arguments[1].params[1] === undefined) ||
n.Identifier.check(expression.arguments[1].params[1])) &&
(expression.arguments[2] === undefined ||
n.ThisExpression.check(expression.arguments[2]))
) {
const old = { inLoop, replaceReturn };
inLoop = false;
replaceReturn = true;
this.visit(
path
.get("expression")
.get("arguments")
.get(1)
.get("body")
);
inLoop = old.inLoop;
replaceReturn = old.replaceReturn;
const [right, { body, params }] = expression.arguments;
const loop = b.forOfStatement(
b.variableDeclaration("let", [
b.variableDeclarator(
valueOnly ? params[0] : b.arrayPattern([params[1], params[0]])
),
]),
valueOnly
? right
: b.callExpression(
b.memberExpression(right, b.identifier("entries")),
[]
),
checkStatement(body) ? body : b.expressionStatement(body)
);
loop.comments = comments;
path.replace(loop);
changed = true;
}
this.traverse(path);
},
visitForStatement: visitLoop("init", "test", "update"),
visitForInStatement: visitLoop("left", "right"),
visitForOfStatement: visitLoop("left", "right"),
visitFunction(path) {
this.visit(path.get("params"));
const old = { replaceReturn };
replaceReturn = false;
this.visit(path.get("body"));
replaceReturn = old.replaceReturn;
return false;
},
visitReturnStatement(path) {
if (replaceReturn) {
assert(!inLoop); // could use labeled continue if this ever fires
const { argument, comments } = path.node;
if (argument === null) {
const s = b.continueStatement();
s.comments = comments;
path.replace(s);
} else {
const s = b.expressionStatement(argument);
s.comments = comments;
path.replace(s, b.continueStatement());
}
return false;
}
this.traverse(path);
},
visitWhileStatement: visitLoop("test"),
});
if (changed) {
console.log("Writing", file);
fs.writeFileSync(file, recast.print(ast).code, { encoding: "utf8" });
}
}
Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
2020-02-06 06:19:47 +01:00
|
|
|
}
|
2017-07-19 14:39:28 +02:00
|
|
|
};
|
|
|
|
|
2013-10-09 22:42:15 +02:00
|
|
|
exports.register_click_handlers = function () {
|
2020-07-15 01:29:15 +02:00
|
|
|
$("#out-of-view-notification").on("click", ".compose_notification_narrow_by_topic", (e) => {
|
|
|
|
const message_id = $(e.currentTarget).data("message-id");
|
|
|
|
narrow.by_topic(message_id, {trigger: "compose_notification"});
|
2013-10-09 22:42:15 +02:00
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
});
|
2020-07-15 01:29:15 +02:00
|
|
|
$("#out-of-view-notification").on("click", ".compose_notification_scroll_to_message", (e) => {
|
|
|
|
const message_id = $(e.currentTarget).data("message-id");
|
2019-08-28 17:46:10 +02:00
|
|
|
current_msg_list.select_id(message_id);
|
2016-05-25 13:26:57 +02:00
|
|
|
navigate.scroll_to_selected();
|
2013-11-13 19:40:02 +01:00
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
});
|
2020-07-15 01:29:15 +02:00
|
|
|
$("#out-of-view-notification").on("click", ".out-of-view-notification-close", (e) => {
|
2013-11-13 19:40:02 +01:00
|
|
|
exports.clear_compose_notifications();
|
2013-10-09 22:42:15 +02:00
|
|
|
e.stopPropagation();
|
|
|
|
e.preventDefault();
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
2014-02-13 23:48:03 +01:00
|
|
|
exports.handle_global_notification_updates = function (notification_name, setting) {
|
|
|
|
// Update the global settings checked when determining if we should notify
|
|
|
|
// for a given message. These settings do not affect whether or not a
|
|
|
|
// particular stream should receive notifications.
|
2020-03-28 18:03:43 +01:00
|
|
|
if (settings_config.all_notification_settings.includes(notification_name)) {
|
2018-03-08 07:50:16 +01:00
|
|
|
page_params[notification_name] = setting;
|
2014-02-13 23:48:03 +01:00
|
|
|
}
|
2018-01-11 21:36:11 +01:00
|
|
|
|
2020-03-28 18:03:43 +01:00
|
|
|
if (settings_config.stream_notification_settings.includes(notification_name)) {
|
2020-02-04 13:15:09 +01:00
|
|
|
notification_name = notification_name.replace("enable_stream_", "");
|
|
|
|
stream_ui_updates.update_notification_setting_checkbox(notification_name);
|
|
|
|
}
|
|
|
|
|
2018-01-11 21:36:11 +01:00
|
|
|
if (notification_name === "notification_sound") {
|
|
|
|
// Change the sound source with the new page `notification_sound`.
|
|
|
|
update_notification_sound_source();
|
|
|
|
}
|
2014-02-13 23:48:03 +01:00
|
|
|
};
|
|
|
|
|
2019-10-25 09:45:13 +02:00
|
|
|
window.notifications = exports;
|