mirror of https://github.com/zulip/zulip.git
popover_menus: Extract message_actions_popover module.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
parent
26e34a163e
commit
16e2a32c4c
|
@ -113,6 +113,7 @@ EXEMPT_FILES = make_set(
|
|||
"web/src/loading.ts",
|
||||
"web/src/local_message.js",
|
||||
"web/src/localstorage.ts",
|
||||
"web/src/message_actions_popover.js",
|
||||
"web/src/message_edit.js",
|
||||
"web/src/message_edit_history.js",
|
||||
"web/src/message_events.js",
|
||||
|
|
|
@ -24,6 +24,7 @@ import * as hotspots from "./hotspots";
|
|||
import * as inbox_ui from "./inbox_ui";
|
||||
import * as lightbox from "./lightbox";
|
||||
import * as list_util from "./list_util";
|
||||
import * as message_actions_popover from "./message_actions_popover";
|
||||
import * as message_edit from "./message_edit";
|
||||
import * as message_edit_history from "./message_edit_history";
|
||||
import * as message_lists from "./message_lists";
|
||||
|
@ -1007,7 +1008,7 @@ export function process_hotkey(e, hotkey) {
|
|||
// Shortcuts that operate on a message
|
||||
switch (event_name) {
|
||||
case "message_actions":
|
||||
return popover_menus.toggle_message_actions_menu(msg);
|
||||
return message_actions_popover.toggle_message_actions_menu(msg);
|
||||
case "star_message":
|
||||
starred_messages_ui.toggle_starred_and_update_server(msg);
|
||||
return true;
|
||||
|
|
|
@ -0,0 +1,216 @@
|
|||
import ClipboardJS from "clipboard";
|
||||
import $ from "jquery";
|
||||
|
||||
import render_actions_popover_content from "../templates/actions_popover_content.hbs";
|
||||
|
||||
import * as blueslip from "./blueslip";
|
||||
import * as compose_actions from "./compose_actions";
|
||||
import * as condense from "./condense";
|
||||
import {show_copied_confirmation} from "./copied_tooltip";
|
||||
import * as emoji_picker from "./emoji_picker";
|
||||
import * as message_edit from "./message_edit";
|
||||
import * as message_lists from "./message_lists";
|
||||
import * as message_viewport from "./message_viewport";
|
||||
import * as popover_menus from "./popover_menus";
|
||||
import * as popover_menus_data from "./popover_menus_data";
|
||||
import * as read_receipts from "./read_receipts";
|
||||
import * as rows from "./rows";
|
||||
import * as stream_popover from "./stream_popover";
|
||||
import {parse_html} from "./ui_util";
|
||||
import * as unread_ops from "./unread_ops";
|
||||
|
||||
let message_actions_popover_keyboard_toggle = false;
|
||||
|
||||
function get_action_menu_menu_items() {
|
||||
const $current_actions_popover_elem = $("[data-tippy-root] .actions_popover");
|
||||
if (!$current_actions_popover_elem) {
|
||||
blueslip.error("Trying to get menu items when action popover is closed.");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return $current_actions_popover_elem.find("li:not(.divider):visible a");
|
||||
}
|
||||
|
||||
function focus_first_action_popover_item() {
|
||||
// For now I recommend only calling this when the user opens the menu with a hotkey.
|
||||
// Our popup menus act kind of funny when you mix keyboard and mouse.
|
||||
const $items = get_action_menu_menu_items();
|
||||
popover_menus.focus_first_popover_item($items);
|
||||
}
|
||||
|
||||
export function toggle_message_actions_menu(message) {
|
||||
if (message.locally_echoed || message_edit.is_editing(message.id)) {
|
||||
// Don't open the popup for locally echoed messages for now.
|
||||
// It creates bugs with things like keyboard handlers when
|
||||
// we get the server response.
|
||||
// We also suppress the popup for messages in an editing state,
|
||||
// including previews, when a user tries to reach them from the
|
||||
// keyboard.
|
||||
return true;
|
||||
}
|
||||
|
||||
message_viewport.maybe_scroll_to_show_message_top();
|
||||
const $popover_reference = $(".selected_message .actions_hover .message-actions-menu-button");
|
||||
message_actions_popover_keyboard_toggle = true;
|
||||
$popover_reference.trigger("click");
|
||||
return true;
|
||||
}
|
||||
|
||||
export function initialize() {
|
||||
popover_menus.register_popover_menu(".actions_hover .message-actions-menu-button", {
|
||||
// 320px is our minimum supported width for mobile. We will allow the value to flex
|
||||
// to a max of 350px but we shouldn't make the popover wider than this.
|
||||
maxWidth: "min(max(320px, 100vw), 350px)",
|
||||
placement: "bottom",
|
||||
popperOptions: {
|
||||
modifiers: [
|
||||
{
|
||||
// The placement is set to bottom, but if that placement does not fit,
|
||||
// the opposite top placement will be used.
|
||||
name: "flip",
|
||||
options: {
|
||||
fallbackPlacements: ["top", "left"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
onShow(instance) {
|
||||
popover_menus.on_show_prep(instance);
|
||||
const $row = $(instance.reference).closest(".message_row");
|
||||
const message_id = rows.id($row);
|
||||
message_lists.current.select_id(message_id);
|
||||
const args = popover_menus_data.get_actions_popover_content_context(message_id);
|
||||
instance.setContent(parse_html(render_actions_popover_content(args)));
|
||||
$row.addClass("has_actions_popover");
|
||||
},
|
||||
onMount(instance) {
|
||||
if (message_actions_popover_keyboard_toggle) {
|
||||
focus_first_action_popover_item();
|
||||
message_actions_popover_keyboard_toggle = false;
|
||||
}
|
||||
popover_menus.popover_instances.message_actions = instance;
|
||||
|
||||
// We want click events to propagate to `instance` so that
|
||||
// instance.hide gets called.
|
||||
const $popper = $(instance.popper);
|
||||
$popper.one("click", ".respond_button", (e) => {
|
||||
// Arguably, we should fetch the message ID to respond to from
|
||||
// e.target, but that should always be the current selected
|
||||
// message in the current message list (and
|
||||
// compose_actions.respond_to_message doesn't take a message
|
||||
// argument).
|
||||
compose_actions.quote_and_reply({trigger: "popover respond"});
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
$popper.one("click", ".popover_edit_message, .popover_view_source", (e) => {
|
||||
const message_id = $(e.currentTarget).data("message-id");
|
||||
const $row = message_lists.current.get_row(message_id);
|
||||
message_edit.start($row);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
$popper.one("click", ".popover_move_message", (e) => {
|
||||
const message_id = $(e.currentTarget).data("message-id");
|
||||
const message = message_lists.current.get(message_id);
|
||||
stream_popover.build_move_topic_to_stream_popover(
|
||||
message.stream_id,
|
||||
message.topic,
|
||||
false,
|
||||
message,
|
||||
);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
$popper.one("click", ".mark_as_unread", (e) => {
|
||||
const message_id = $(e.currentTarget).data("message-id");
|
||||
unread_ops.mark_as_unread_from_here(message_id);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
$popper.one("click", ".popover_toggle_collapse", (e) => {
|
||||
const message_id = $(e.currentTarget).data("message-id");
|
||||
const $row = message_lists.current.get_row(message_id);
|
||||
const message = message_lists.current.get(rows.id($row));
|
||||
if ($row) {
|
||||
if (message.collapsed) {
|
||||
condense.uncollapse($row);
|
||||
} else {
|
||||
condense.collapse($row);
|
||||
}
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
$popper.one("click", ".rehide_muted_user_message", (e) => {
|
||||
const message_id = $(e.currentTarget).data("message-id");
|
||||
const $row = message_lists.current.get_row(message_id);
|
||||
const message = message_lists.current.get(rows.id($row));
|
||||
const message_container = message_lists.current.view.message_containers.get(
|
||||
message.id,
|
||||
);
|
||||
if ($row && !message_container.is_hidden) {
|
||||
message_lists.current.view.hide_revealed_message(message_id);
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
$popper.one("click", ".view_read_receipts", (e) => {
|
||||
const message_id = $(e.currentTarget).data("message-id");
|
||||
read_receipts.show_user_list(message_id);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
$popper.one("click", ".delete_message", (e) => {
|
||||
const message_id = $(e.currentTarget).data("message-id");
|
||||
message_edit.delete_message(message_id);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
$popper.one("click", ".reaction_button", (e) => {
|
||||
const message_id = $(e.currentTarget).data("message-id");
|
||||
// Don't propagate the click event since `toggle_emoji_popover` opens a
|
||||
// emoji_picker which we don't want to hide after actions popover is hidden.
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
emoji_picker.toggle_emoji_popover(instance.reference.parentElement, message_id, {
|
||||
placement: "bottom",
|
||||
});
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
new ClipboardJS($popper.find(".copy_link")[0]).on("success", () => {
|
||||
show_copied_confirmation($(instance.reference).closest(".message_controls")[0]);
|
||||
setTimeout(() => {
|
||||
// The Clipboard library works by focusing to a hidden textarea.
|
||||
// We unfocus this so keyboard shortcuts, etc., will work again.
|
||||
$(":focus").trigger("blur");
|
||||
}, 0);
|
||||
instance.hide();
|
||||
});
|
||||
},
|
||||
onHidden(instance) {
|
||||
const $row = $(instance.reference).closest(".message_row");
|
||||
$row.removeClass("has_actions_popover");
|
||||
instance.destroy();
|
||||
popover_menus.popover_instances.message_actions = undefined;
|
||||
message_actions_popover_keyboard_toggle = false;
|
||||
},
|
||||
});
|
||||
}
|
|
@ -6,7 +6,6 @@ import ClipboardJS from "clipboard";
|
|||
import $ from "jquery";
|
||||
import tippy, {delegate} from "tippy.js";
|
||||
|
||||
import render_actions_popover_content from "../templates/actions_popover_content.hbs";
|
||||
import render_all_messages_sidebar_actions from "../templates/all_messages_sidebar_actions.hbs";
|
||||
import render_change_visibility_policy_popover from "../templates/change_visibility_policy_popover.hbs";
|
||||
import render_compose_control_buttons_popover from "../templates/compose_control_buttons_popover.hbs";
|
||||
|
@ -21,22 +20,16 @@ import * as blueslip from "./blueslip";
|
|||
import * as channel from "./channel";
|
||||
import * as common from "./common";
|
||||
import * as compose_actions from "./compose_actions";
|
||||
import * as condense from "./condense";
|
||||
import * as confirm_dialog from "./confirm_dialog";
|
||||
import {show_copied_confirmation} from "./copied_tooltip";
|
||||
import * as drafts from "./drafts";
|
||||
import * as emoji_picker from "./emoji_picker";
|
||||
import * as giphy from "./giphy";
|
||||
import {$t_html} from "./i18n";
|
||||
import * as message_edit from "./message_edit";
|
||||
import * as message_lists from "./message_lists";
|
||||
import * as message_viewport from "./message_viewport";
|
||||
import * as narrow_state from "./narrow_state";
|
||||
import * as overlays from "./overlays";
|
||||
import {page_params} from "./page_params";
|
||||
import * as popover_menus_data from "./popover_menus_data";
|
||||
import * as popovers from "./popovers";
|
||||
import * as read_receipts from "./read_receipts";
|
||||
import * as rows from "./rows";
|
||||
import * as starred_messages from "./starred_messages";
|
||||
import * as starred_messages_ui from "./starred_messages_ui";
|
||||
|
@ -46,8 +39,6 @@ import * as unread_ops from "./unread_ops";
|
|||
import {user_settings} from "./user_settings";
|
||||
import * as user_topics from "./user_topics";
|
||||
|
||||
let message_actions_popover_keyboard_toggle = false;
|
||||
|
||||
// On mobile web, opening the keyboard can trigger a resize event
|
||||
// (which in turn can trigger a scroll event). This will have the
|
||||
// side effect of closing popovers, which we don't want. So we
|
||||
|
@ -104,23 +95,6 @@ export function focus_first_popover_item($items, index = 0) {
|
|||
$items.eq(index).expectOne().trigger("focus");
|
||||
}
|
||||
|
||||
function get_action_menu_menu_items() {
|
||||
const $current_actions_popover_elem = $("[data-tippy-root] .actions_popover");
|
||||
if (!$current_actions_popover_elem) {
|
||||
blueslip.error("Trying to get menu items when action popover is closed.");
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return $current_actions_popover_elem.find("li:not(.divider):visible a");
|
||||
}
|
||||
|
||||
function focus_first_action_popover_item() {
|
||||
// For now I recommend only calling this when the user opens the menu with a hotkey.
|
||||
// Our popup menus act kind of funny when you mix keyboard and mouse.
|
||||
const $items = get_action_menu_menu_items();
|
||||
focus_first_popover_item($items);
|
||||
}
|
||||
|
||||
export function sidebar_menu_instance_handle_keyboard(instance, key) {
|
||||
const items = get_popover_items_for_instance(instance);
|
||||
popover_items_handle_keyboard(key, items);
|
||||
|
@ -309,24 +283,6 @@ export function register_popover_menu(target, popover_props) {
|
|||
});
|
||||
}
|
||||
|
||||
export function toggle_message_actions_menu(message) {
|
||||
if (message.locally_echoed || message_edit.is_editing(message.id)) {
|
||||
// Don't open the popup for locally echoed messages for now.
|
||||
// It creates bugs with things like keyboard handlers when
|
||||
// we get the server response.
|
||||
// We also suppress the popup for messages in an editing state,
|
||||
// including previews, when a user tries to reach them from the
|
||||
// keyboard.
|
||||
return true;
|
||||
}
|
||||
|
||||
message_viewport.maybe_scroll_to_show_message_top();
|
||||
const $popover_reference = $(".selected_message .actions_hover .message-actions-menu-button");
|
||||
message_actions_popover_keyboard_toggle = true;
|
||||
$popover_reference.trigger("click");
|
||||
return true;
|
||||
}
|
||||
|
||||
export function initialize() {
|
||||
// compose box buttons popover shown on mobile widths.
|
||||
// We want this click event to propagate and hide other popovers
|
||||
|
@ -656,163 +612,6 @@ export function initialize() {
|
|||
},
|
||||
});
|
||||
|
||||
register_popover_menu(".actions_hover .message-actions-menu-button", {
|
||||
// 320px is our minimum supported width for mobile. We will allow the value to flex
|
||||
// to a max of 350px but we shouldn't make the popover wider than this.
|
||||
maxWidth: "min(max(320px, 100vw), 350px)",
|
||||
placement: "bottom",
|
||||
popperOptions: {
|
||||
modifiers: [
|
||||
{
|
||||
// The placement is set to bottom, but if that placement does not fit,
|
||||
// the opposite top placement will be used.
|
||||
name: "flip",
|
||||
options: {
|
||||
fallbackPlacements: ["top", "left"],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
onShow(instance) {
|
||||
on_show_prep(instance);
|
||||
const $row = $(instance.reference).closest(".message_row");
|
||||
const message_id = rows.id($row);
|
||||
message_lists.current.select_id(message_id);
|
||||
const args = popover_menus_data.get_actions_popover_content_context(message_id);
|
||||
instance.setContent(parse_html(render_actions_popover_content(args)));
|
||||
$row.addClass("has_actions_popover");
|
||||
},
|
||||
onMount(instance) {
|
||||
if (message_actions_popover_keyboard_toggle) {
|
||||
focus_first_action_popover_item();
|
||||
message_actions_popover_keyboard_toggle = false;
|
||||
}
|
||||
popover_instances.message_actions = instance;
|
||||
|
||||
// We want click events to propagate to `instance` so that
|
||||
// instance.hide gets called.
|
||||
const $popper = $(instance.popper);
|
||||
$popper.one("click", ".respond_button", (e) => {
|
||||
// Arguably, we should fetch the message ID to respond to from
|
||||
// e.target, but that should always be the current selected
|
||||
// message in the current message list (and
|
||||
// compose_actions.respond_to_message doesn't take a message
|
||||
// argument).
|
||||
compose_actions.quote_and_reply({trigger: "popover respond"});
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
$popper.one("click", ".popover_edit_message, .popover_view_source", (e) => {
|
||||
const message_id = $(e.currentTarget).data("message-id");
|
||||
const $row = message_lists.current.get_row(message_id);
|
||||
message_edit.start($row);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
$popper.one("click", ".popover_move_message", (e) => {
|
||||
const message_id = $(e.currentTarget).data("message-id");
|
||||
const message = message_lists.current.get(message_id);
|
||||
stream_popover.build_move_topic_to_stream_popover(
|
||||
message.stream_id,
|
||||
message.topic,
|
||||
false,
|
||||
message,
|
||||
);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
$popper.one("click", ".mark_as_unread", (e) => {
|
||||
const message_id = $(e.currentTarget).data("message-id");
|
||||
unread_ops.mark_as_unread_from_here(message_id);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
$popper.one("click", ".popover_toggle_collapse", (e) => {
|
||||
const message_id = $(e.currentTarget).data("message-id");
|
||||
const $row = message_lists.current.get_row(message_id);
|
||||
const message = message_lists.current.get(rows.id($row));
|
||||
if ($row) {
|
||||
if (message.collapsed) {
|
||||
condense.uncollapse($row);
|
||||
} else {
|
||||
condense.collapse($row);
|
||||
}
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
$popper.one("click", ".rehide_muted_user_message", (e) => {
|
||||
const message_id = $(e.currentTarget).data("message-id");
|
||||
const $row = message_lists.current.get_row(message_id);
|
||||
const message = message_lists.current.get(rows.id($row));
|
||||
const message_container = message_lists.current.view.message_containers.get(
|
||||
message.id,
|
||||
);
|
||||
if ($row && !message_container.is_hidden) {
|
||||
message_lists.current.view.hide_revealed_message(message_id);
|
||||
}
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
$popper.one("click", ".view_read_receipts", (e) => {
|
||||
const message_id = $(e.currentTarget).data("message-id");
|
||||
read_receipts.show_user_list(message_id);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
$popper.one("click", ".delete_message", (e) => {
|
||||
const message_id = $(e.currentTarget).data("message-id");
|
||||
message_edit.delete_message(message_id);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
$popper.one("click", ".reaction_button", (e) => {
|
||||
const message_id = $(e.currentTarget).data("message-id");
|
||||
// Don't propagate the click event since `toggle_emoji_popover` opens a
|
||||
// emoji_picker which we don't want to hide after actions popover is hidden.
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
emoji_picker.toggle_emoji_popover(instance.reference.parentElement, message_id, {
|
||||
placement: "bottom",
|
||||
});
|
||||
instance.hide();
|
||||
});
|
||||
|
||||
new ClipboardJS($popper.find(".copy_link")[0]).on("success", () => {
|
||||
show_copied_confirmation($(instance.reference).closest(".message_controls")[0]);
|
||||
setTimeout(() => {
|
||||
// The Clipboard library works by focusing to a hidden textarea.
|
||||
// We unfocus this so keyboard shortcuts, etc., will work again.
|
||||
$(":focus").trigger("blur");
|
||||
}, 0);
|
||||
instance.hide();
|
||||
});
|
||||
},
|
||||
onHidden(instance) {
|
||||
const $row = $(instance.reference).closest(".message_row");
|
||||
$row.removeClass("has_actions_popover");
|
||||
instance.destroy();
|
||||
popover_instances.message_actions = undefined;
|
||||
message_actions_popover_keyboard_toggle = false;
|
||||
},
|
||||
});
|
||||
|
||||
// Starred messages popover
|
||||
register_popover_menu(".starred-messages-sidebar-menu-icon", {
|
||||
...left_sidebar_tippy_options,
|
||||
|
|
|
@ -49,6 +49,7 @@ import * as linkifiers from "./linkifiers";
|
|||
import {localstorage} from "./localstorage";
|
||||
import * as markdown from "./markdown";
|
||||
import * as markdown_config from "./markdown_config";
|
||||
import * as message_actions_popover from "./message_actions_popover";
|
||||
import * as message_edit_history from "./message_edit_history";
|
||||
import * as message_fetch from "./message_fetch";
|
||||
import * as message_list from "./message_list";
|
||||
|
@ -526,6 +527,7 @@ export function initialize_everything() {
|
|||
// This populates data for scheduled messages.
|
||||
scheduled_messages.initialize(scheduled_messages_params);
|
||||
popover_menus.initialize();
|
||||
message_actions_popover.initialize();
|
||||
scheduled_messages_popover.initialize();
|
||||
|
||||
realm_user_settings_defaults.initialize(realm_settings_defaults_params);
|
||||
|
|
|
@ -45,6 +45,7 @@ const gear_menu = mock_esm("../src/gear_menu", {
|
|||
});
|
||||
const lightbox = mock_esm("../src/lightbox");
|
||||
const list_util = mock_esm("../src/list_util");
|
||||
const message_actions_popover = mock_esm("../src/message_actions_popover");
|
||||
const message_edit = mock_esm("../src/message_edit");
|
||||
const message_lists = mock_esm("../src/message_lists");
|
||||
const user_topics_ui = mock_esm("../src/user_topics_ui");
|
||||
|
@ -79,9 +80,6 @@ const popovers = mock_esm("../src/user_card_popover", {
|
|||
is_open: () => false,
|
||||
},
|
||||
});
|
||||
const popover_menus = mock_esm("../src/popover_menus", {
|
||||
get_visible_instance: () => undefined,
|
||||
});
|
||||
const reactions = mock_esm("../src/reactions");
|
||||
const search = mock_esm("../src/search");
|
||||
const settings_data = mock_esm("../src/settings_data");
|
||||
|
@ -385,7 +383,7 @@ run_test("misc", ({override}) => {
|
|||
assert_mapping("k", navigate, "up");
|
||||
assert_mapping("K", navigate, "page_up");
|
||||
assert_mapping("u", popovers, "toggle_sender_info");
|
||||
assert_mapping("i", popover_menus, "toggle_message_actions_menu");
|
||||
assert_mapping("i", message_actions_popover, "toggle_message_actions_menu");
|
||||
assert_mapping(":", emoji_picker, "toggle_emoji_popover", true);
|
||||
assert_mapping(">", compose_actions, "quote_and_reply");
|
||||
assert_mapping("e", message_edit, "start");
|
||||
|
|
Loading…
Reference in New Issue