mirror of https://github.com/zulip/zulip.git
parent
8301dcd421
commit
66113365a5
|
@ -196,6 +196,7 @@ EXEMPT_FILES = make_set(
|
||||||
"web/src/resize.ts",
|
"web/src/resize.ts",
|
||||||
"web/src/resize_handler.ts",
|
"web/src/resize_handler.ts",
|
||||||
"web/src/rows.ts",
|
"web/src/rows.ts",
|
||||||
|
"web/src/saved_snippets_ui.ts",
|
||||||
"web/src/scheduled_messages.ts",
|
"web/src/scheduled_messages.ts",
|
||||||
"web/src/scheduled_messages_feed_ui.ts",
|
"web/src/scheduled_messages_feed_ui.ts",
|
||||||
"web/src/scheduled_messages_overlay_ui.js",
|
"web/src/scheduled_messages_overlay_ui.js",
|
||||||
|
|
|
@ -145,7 +145,7 @@ export class DropdownWidget {
|
||||||
`${this.widget_selector}, ${this.widget_wrapper_id}`,
|
`${this.widget_selector}, ${this.widget_wrapper_id}`,
|
||||||
(e) => {
|
(e) => {
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
$(this.widget_selector).trigger("click");
|
$(this.widget_selector)[0]?.click();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
import * as blueslip from "./blueslip";
|
||||||
|
import type {Option} from "./dropdown_widget";
|
||||||
|
import {$t} from "./i18n";
|
||||||
|
import type {StateData} from "./state_data";
|
||||||
|
import * as util from "./util";
|
||||||
|
|
||||||
|
export type SavedSnippet = {
|
||||||
|
id: number;
|
||||||
|
title: string;
|
||||||
|
content: string;
|
||||||
|
date_created: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ADD_SAVED_SNIPPET_OPTION_ID = -1;
|
||||||
|
let saved_snippets_dict: Map<number, SavedSnippet>;
|
||||||
|
|
||||||
|
export function get_saved_snippet_by_id(saved_snippet_id: number): SavedSnippet | undefined {
|
||||||
|
const saved_snippet = saved_snippets_dict.get(saved_snippet_id);
|
||||||
|
if (saved_snippet === undefined) {
|
||||||
|
blueslip.error("Could not find saved snippet", {saved_snippet_id});
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return saved_snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function add_saved_snippet(saved_snippet: SavedSnippet): void {
|
||||||
|
saved_snippets_dict.set(saved_snippet.id, saved_snippet);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function remove_saved_snippet(saved_snippet_id: number): void {
|
||||||
|
saved_snippets_dict.delete(saved_snippet_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function get_options_for_dropdown_widget(): Option[] {
|
||||||
|
const saved_snippets = [...saved_snippets_dict.values()].sort((a, b) =>
|
||||||
|
util.strcmp(a.title.toLowerCase(), b.title.toLowerCase()),
|
||||||
|
);
|
||||||
|
const options = saved_snippets.map((saved_snippet) => ({
|
||||||
|
unique_id: saved_snippet.id,
|
||||||
|
name: saved_snippet.title,
|
||||||
|
description: saved_snippet.content,
|
||||||
|
bold_current_selection: true,
|
||||||
|
has_delete_icon: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Option for creating a new saved snippet.
|
||||||
|
options.unshift({
|
||||||
|
unique_id: ADD_SAVED_SNIPPET_OPTION_ID,
|
||||||
|
name: $t({defaultMessage: "Add a new saved snippet"}),
|
||||||
|
description: "",
|
||||||
|
bold_current_selection: true,
|
||||||
|
has_delete_icon: false,
|
||||||
|
});
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialize = (params: StateData["saved_snippets"]): void => {
|
||||||
|
saved_snippets_dict = new Map<number, SavedSnippet>(
|
||||||
|
params.saved_snippets.map((s) => [s.id, s]),
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,131 @@
|
||||||
|
import $ from "jquery";
|
||||||
|
import assert from "minimalistic-assert";
|
||||||
|
import type * as tippy from "tippy.js";
|
||||||
|
|
||||||
|
import render_add_saved_snippet_modal from "../templates/add_saved_snippet_modal.hbs";
|
||||||
|
import render_confirm_delete_saved_snippet from "../templates/confirm_dialog/confirm_delete_saved_snippet.hbs";
|
||||||
|
|
||||||
|
import * as channel from "./channel";
|
||||||
|
import * as compose_ui from "./compose_ui";
|
||||||
|
import * as confirm_dialog from "./confirm_dialog";
|
||||||
|
import * as dialog_widget from "./dialog_widget";
|
||||||
|
import * as dropdown_widget from "./dropdown_widget";
|
||||||
|
import {$t_html} from "./i18n";
|
||||||
|
import * as popover_menus from "./popover_menus";
|
||||||
|
import * as saved_snippets from "./saved_snippets";
|
||||||
|
import type {StateData} from "./state_data";
|
||||||
|
|
||||||
|
let saved_snippet_dropdown_widget: dropdown_widget.DropdownWidget;
|
||||||
|
|
||||||
|
function submit_create_saved_snippet_form(): void {
|
||||||
|
const title = $<HTMLInputElement>("#add-new-saved-snippet-modal .saved-snippet-title")
|
||||||
|
.val()
|
||||||
|
?.trim();
|
||||||
|
const content = $<HTMLInputElement>("#add-new-saved-snippet-modal .saved-snippet-content")
|
||||||
|
.val()
|
||||||
|
?.trim();
|
||||||
|
if (title && content) {
|
||||||
|
dialog_widget.submit_api_request(channel.post, "/json/saved_snippets", {title, content});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_submit_button_state(): void {
|
||||||
|
const title = $<HTMLInputElement>("#add-new-saved-snippet-modal .saved-snippet-title")
|
||||||
|
.val()
|
||||||
|
?.trim();
|
||||||
|
const content = $<HTMLInputElement>("#add-new-saved-snippet-modal .saved-snippet-content")
|
||||||
|
.val()
|
||||||
|
?.trim();
|
||||||
|
const $submit_button = $("#add-new-saved-snippet-modal .dialog_submit_button");
|
||||||
|
|
||||||
|
$submit_button.prop("disabled", true);
|
||||||
|
if (title && content) {
|
||||||
|
$submit_button.prop("disabled", false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function saved_snippet_modal_post_render(): void {
|
||||||
|
$("#add-new-saved-snippet-modal").on("input", "input,textarea", update_submit_button_state);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function rerender_dropdown_widget(): void {
|
||||||
|
const options = saved_snippets.get_options_for_dropdown_widget();
|
||||||
|
saved_snippet_dropdown_widget.list_widget?.replace_list_data(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete_saved_snippet(saved_snippet_id: string): void {
|
||||||
|
void channel.del({
|
||||||
|
url: "/json/saved_snippets/" + saved_snippet_id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function item_click_callback(
|
||||||
|
event: JQuery.ClickEvent,
|
||||||
|
dropdown: tippy.Instance,
|
||||||
|
widget: dropdown_widget.DropdownWidget,
|
||||||
|
): void {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
|
||||||
|
if (
|
||||||
|
$(event.target).closest(".saved_snippets-dropdown-list-container .dropdown-list-delete")
|
||||||
|
.length
|
||||||
|
) {
|
||||||
|
confirm_dialog.launch({
|
||||||
|
html_heading: $t_html({defaultMessage: "Delete saved snippet?"}),
|
||||||
|
html_body: render_confirm_delete_saved_snippet(),
|
||||||
|
on_click() {
|
||||||
|
const saved_snippet_id = $(event.currentTarget).attr("data-unique-id");
|
||||||
|
assert(saved_snippet_id !== undefined);
|
||||||
|
delete_saved_snippet(saved_snippet_id);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dropdown.hide();
|
||||||
|
// Hide `send_later` popover when a saved snippet is clicked.
|
||||||
|
popover_menus.hide_current_popover_if_visible(popover_menus.popover_instances.send_later);
|
||||||
|
const current_value = widget.current_value;
|
||||||
|
assert(typeof current_value === "number");
|
||||||
|
if (current_value === saved_snippets.ADD_SAVED_SNIPPET_OPTION_ID) {
|
||||||
|
dialog_widget.launch({
|
||||||
|
html_heading: $t_html({defaultMessage: "Add a new saved snippet"}),
|
||||||
|
html_body: render_add_saved_snippet_modal(),
|
||||||
|
html_submit_button: $t_html({defaultMessage: "Save"}),
|
||||||
|
id: "add-new-saved-snippet-modal",
|
||||||
|
form_id: "add-new-saved-snippet-form",
|
||||||
|
update_submit_disabled_state_on_change: true,
|
||||||
|
on_click: submit_create_saved_snippet_form,
|
||||||
|
on_shown: () => $("#add-saved-snippet-title").trigger("focus"),
|
||||||
|
post_render: saved_snippet_modal_post_render,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const saved_snippet = saved_snippets.get_saved_snippet_by_id(current_value);
|
||||||
|
assert(saved_snippet !== undefined);
|
||||||
|
const content = saved_snippet.content;
|
||||||
|
const $textarea = $<HTMLTextAreaElement>("textarea#compose-textarea");
|
||||||
|
compose_ui.insert_syntax_and_focus(content, $textarea);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const initialize = (params: StateData["saved_snippets"]): void => {
|
||||||
|
saved_snippets.initialize(params);
|
||||||
|
|
||||||
|
saved_snippet_dropdown_widget = new dropdown_widget.DropdownWidget({
|
||||||
|
widget_name: "saved_snippets",
|
||||||
|
get_options: saved_snippets.get_options_for_dropdown_widget,
|
||||||
|
item_click_callback,
|
||||||
|
$events_container: $("body"),
|
||||||
|
unique_id_type: dropdown_widget.DataTypes.NUMBER,
|
||||||
|
focus_target_on_hidden: false,
|
||||||
|
prefer_top_start_placement: true,
|
||||||
|
tippy_props: {
|
||||||
|
// Using -100 as x offset makes saved snippet icon be in the center
|
||||||
|
// of the dropdown widget and 5 as y offset is what we use in compose
|
||||||
|
// recipient dropdown widget.
|
||||||
|
offset: [-100, 5],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
saved_snippet_dropdown_widget.setup();
|
||||||
|
};
|
|
@ -43,6 +43,8 @@ import * as realm_logo from "./realm_logo";
|
||||||
import * as realm_playground from "./realm_playground";
|
import * as realm_playground from "./realm_playground";
|
||||||
import {realm_user_settings_defaults} from "./realm_user_settings_defaults";
|
import {realm_user_settings_defaults} from "./realm_user_settings_defaults";
|
||||||
import * as reload from "./reload";
|
import * as reload from "./reload";
|
||||||
|
import * as saved_snippets from "./saved_snippets";
|
||||||
|
import * as saved_snippets_ui from "./saved_snippets_ui";
|
||||||
import * as scheduled_messages from "./scheduled_messages";
|
import * as scheduled_messages from "./scheduled_messages";
|
||||||
import * as scheduled_messages_feed_ui from "./scheduled_messages_feed_ui";
|
import * as scheduled_messages_feed_ui from "./scheduled_messages_feed_ui";
|
||||||
import * as scheduled_messages_overlay_ui from "./scheduled_messages_overlay_ui";
|
import * as scheduled_messages_overlay_ui from "./scheduled_messages_overlay_ui";
|
||||||
|
@ -503,6 +505,18 @@ export function dispatch_normal_event(event) {
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case "saved_snippets":
|
||||||
|
switch (event.op) {
|
||||||
|
case "add":
|
||||||
|
saved_snippets.add_saved_snippet(event.saved_snippet);
|
||||||
|
saved_snippets_ui.rerender_dropdown_widget();
|
||||||
|
break;
|
||||||
|
case "remove":
|
||||||
|
saved_snippets.remove_saved_snippet(event.saved_snippet_id);
|
||||||
|
saved_snippets_ui.rerender_dropdown_widget();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case "scheduled_messages":
|
case "scheduled_messages":
|
||||||
switch (event.op) {
|
switch (event.op) {
|
||||||
case "add": {
|
case "add": {
|
||||||
|
|
|
@ -183,6 +183,13 @@ export const presence_schema = z.object({
|
||||||
idle_timestamp: z.number().optional(),
|
idle_timestamp: z.number().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const saved_snippet_schema = z.object({
|
||||||
|
id: z.number(),
|
||||||
|
title: z.string(),
|
||||||
|
content: z.string(),
|
||||||
|
date_created: z.number(),
|
||||||
|
});
|
||||||
|
|
||||||
const one_time_notice_schema = z.object({
|
const one_time_notice_schema = z.object({
|
||||||
name: z.string(),
|
name: z.string(),
|
||||||
type: z.literal("one_time_notice"),
|
type: z.literal("one_time_notice"),
|
||||||
|
@ -445,6 +452,11 @@ export const state_data_schema = z
|
||||||
})
|
})
|
||||||
.transform((presence) => ({presence})),
|
.transform((presence) => ({presence})),
|
||||||
)
|
)
|
||||||
|
.and(
|
||||||
|
z
|
||||||
|
.object({saved_snippets: z.array(saved_snippet_schema)})
|
||||||
|
.transform((saved_snippets) => ({saved_snippets})),
|
||||||
|
)
|
||||||
.and(
|
.and(
|
||||||
z
|
z
|
||||||
.object({starred_messages: z.array(z.number())})
|
.object({starred_messages: z.array(z.number())})
|
||||||
|
|
|
@ -788,4 +788,14 @@ export function initialize(): void {
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
tippy.delegate("body", {
|
||||||
|
target: ".saved_snippets-dropdown-list-container .dropdown-list-delete",
|
||||||
|
content: $t({defaultMessage: "Delete snippet"}),
|
||||||
|
delay: LONG_HOVER_DELAY,
|
||||||
|
appendTo: () => document.body,
|
||||||
|
onHidden(instance) {
|
||||||
|
instance.destroy();
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -95,6 +95,7 @@ import * as realm_user_settings_defaults from "./realm_user_settings_defaults";
|
||||||
import * as recent_view_ui from "./recent_view_ui";
|
import * as recent_view_ui from "./recent_view_ui";
|
||||||
import * as reload_setup from "./reload_setup";
|
import * as reload_setup from "./reload_setup";
|
||||||
import * as resize_handler from "./resize_handler";
|
import * as resize_handler from "./resize_handler";
|
||||||
|
import * as saved_snippets_ui from "./saved_snippets_ui";
|
||||||
import * as scheduled_messages from "./scheduled_messages";
|
import * as scheduled_messages from "./scheduled_messages";
|
||||||
import * as scheduled_messages_overlay_ui from "./scheduled_messages_overlay_ui";
|
import * as scheduled_messages_overlay_ui from "./scheduled_messages_overlay_ui";
|
||||||
import * as scheduled_messages_ui from "./scheduled_messages_ui";
|
import * as scheduled_messages_ui from "./scheduled_messages_ui";
|
||||||
|
@ -518,6 +519,7 @@ export function initialize_everything(state_data) {
|
||||||
});
|
});
|
||||||
inbox_ui.initialize();
|
inbox_ui.initialize();
|
||||||
alert_words.initialize(state_data.alert_words);
|
alert_words.initialize(state_data.alert_words);
|
||||||
|
saved_snippets_ui.initialize(state_data.saved_snippets);
|
||||||
emojisets.initialize();
|
emojisets.initialize();
|
||||||
scroll_bar.initialize();
|
scroll_bar.initialize();
|
||||||
message_viewport.initialize();
|
message_viewport.initialize();
|
||||||
|
|
|
@ -1588,6 +1588,51 @@ textarea.new_message_textarea {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.saved_snippets-dropdown-list-container {
|
||||||
|
width: 250px;
|
||||||
|
|
||||||
|
.dropdown-list .dropdown-list-item-common-styles {
|
||||||
|
padding: 5px 10px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.dropdown-list-item-name {
|
||||||
|
line-height: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-list-item-description {
|
||||||
|
white-space: nowrap;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 13px;
|
||||||
|
opacity: 0.8;
|
||||||
|
padding: 0;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-list-bold-selected {
|
||||||
|
font-weight: 500;
|
||||||
|
max-width: 210px;
|
||||||
|
display: inline-block;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#add-new-saved-snippet-modal {
|
||||||
|
& .saved-snippet-title {
|
||||||
|
width: 97%;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
& .saved-snippet-content {
|
||||||
|
width: 97%;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#compose.compose-fullscreen,
|
#compose.compose-fullscreen,
|
||||||
#compose.compose-intermediate {
|
#compose.compose-intermediate {
|
||||||
z-index: 99;
|
z-index: 99;
|
||||||
|
|
|
@ -155,6 +155,19 @@
|
||||||
0 2px 4px hsl(0deg 0% 0% / 20%);
|
0 2px 4px hsl(0deg 0% 0% / 20%);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-list-delete {
|
||||||
|
/* hsl(7deg 100% 74%) corresponds to var(--red-250) */
|
||||||
|
color: color-mix(
|
||||||
|
in oklch,
|
||||||
|
hsl(7deg 100% 74%) 70%,
|
||||||
|
transparent
|
||||||
|
) !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: hsl(7deg 100% 74%) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#navbar-middle .column-middle-inner,
|
#navbar-middle .column-middle-inner,
|
||||||
.header,
|
.header,
|
||||||
#message_view_header {
|
#message_view_header {
|
||||||
|
|
|
@ -1973,12 +1973,29 @@ body:not(.hide-left-sidebar) {
|
||||||
margin-top: 2px;
|
margin-top: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dropdown-list-delete {
|
||||||
|
visibility: hidden;
|
||||||
|
float: right;
|
||||||
|
margin-right: 5px;
|
||||||
|
cursor: pointer;
|
||||||
|
/* hsl(359deg 93% 39%) corresponds to var(--red-550) */
|
||||||
|
color: color-mix(in oklch, hsl(359deg 93% 39%) 70%, transparent);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: hsl(359deg 93% 39%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
&:focus,
|
&:focus,
|
||||||
&:hover {
|
&:hover {
|
||||||
color: var(--color-dropdown-item);
|
color: var(--color-dropdown-item);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
background-color: var(--background-color-active-dropdown-item);
|
background-color: var(--background-color-active-dropdown-item);
|
||||||
outline: none;
|
outline: none;
|
||||||
|
|
||||||
|
.dropdown-list-delete {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div id="add-new-saved-snippet-modal" class="new-style">
|
||||||
|
<form id="add-new-saved-snippet-form" class="new-style">
|
||||||
|
<label for="title" class="modal-field-label">{{t "Title" }}</label>
|
||||||
|
<input id="new-saved-snippet-title" type="text" name="title" class="modal_text_input saved-snippet-title" value="" autocomplete="off" spellcheck="false" autofocus="autofocus"/>
|
||||||
|
<div>{{t "Content" }}</div>
|
||||||
|
<textarea class="settings_textarea saved-snippet-content" rows="4"></textarea>
|
||||||
|
</form>
|
||||||
|
</div>
|
|
@ -0,0 +1 @@
|
||||||
|
<p>{{t "This action cannot be undone."}}</p>
|
|
@ -5,6 +5,11 @@
|
||||||
<span class="dropdown-list-item-name">
|
<span class="dropdown-list-item-name">
|
||||||
{{#if bold_current_selection}}
|
{{#if bold_current_selection}}
|
||||||
<span class="dropdown-list-bold-selected">{{name}}</span>
|
<span class="dropdown-list-bold-selected">{{name}}</span>
|
||||||
|
{{#if has_delete_icon}}
|
||||||
|
<span class="dropdown-list-delete">
|
||||||
|
<i class="fa fa-trash-o dropdown-list-delete-icon"></i>
|
||||||
|
</span>
|
||||||
|
{{/if}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{name}}
|
{{name}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
|
@ -67,6 +67,13 @@
|
||||||
<span class="compose-drafts-count-container"><span class="unread_count compose-drafts-count"></span></span>
|
<span class="compose-drafts-count-container"><span class="unread_count compose-drafts-count"></span></span>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li role="separator" class="popover-menu-separator"></li>
|
||||||
|
<li role="none" class="link-item popover-menu-list-item saved-snippets-item-in-popover">
|
||||||
|
<a id="saved_snippets_widget" role="menuitem" class="view-saved-snippets popover-menu-link" tabindex="0">
|
||||||
|
<i class="popover-menu-icon zulip-icon zulip-icon-message-square" aria-hidden="true"></i>
|
||||||
|
<span class="popover-menu-label">{{t "Saved snippets" }}</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
{{#if show_compose_new_message}}
|
{{#if show_compose_new_message}}
|
||||||
<li role="separator" class="popover-menu-separator"></li>
|
<li role="separator" class="popover-menu-separator"></li>
|
||||||
<li role="none" class="link-item popover-menu-list-item">
|
<li role="none" class="link-item popover-menu-list-item">
|
||||||
|
|
|
@ -65,6 +65,9 @@
|
||||||
<span class="tooltip-inner-content italic">{{t "A poll must be an entire message." }}</span>
|
<span class="tooltip-inner-content italic">{{t "A poll must be an entire message." }}</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template id="add-saved-snippet-tooltip">
|
||||||
|
{{t "Add saved snippet" }}
|
||||||
|
</template>
|
||||||
<template id="link-tooltip">
|
<template id="link-tooltip">
|
||||||
{{t 'Link' }}
|
{{t 'Link' }}
|
||||||
{{tooltip_hotkey_hints "Ctrl" "Shift" "L"}}
|
{{tooltip_hotkey_hints "Ctrl" "Shift" "L"}}
|
||||||
|
|
|
@ -54,6 +54,8 @@ const realm_icon = mock_esm("../src/realm_icon");
|
||||||
const realm_logo = mock_esm("../src/realm_logo");
|
const realm_logo = mock_esm("../src/realm_logo");
|
||||||
const realm_playground = mock_esm("../src/realm_playground");
|
const realm_playground = mock_esm("../src/realm_playground");
|
||||||
const reload = mock_esm("../src/reload");
|
const reload = mock_esm("../src/reload");
|
||||||
|
const saved_snippets = mock_esm("../src/saved_snippets");
|
||||||
|
const saved_snippets_ui = mock_esm("../src/saved_snippets_ui");
|
||||||
const scheduled_messages = mock_esm("../src/scheduled_messages");
|
const scheduled_messages = mock_esm("../src/scheduled_messages");
|
||||||
const scheduled_messages_feed_ui = mock_esm("../src/scheduled_messages_feed_ui");
|
const scheduled_messages_feed_ui = mock_esm("../src/scheduled_messages_feed_ui");
|
||||||
const scheduled_messages_overlay_ui = mock_esm("../src/scheduled_messages_overlay_ui");
|
const scheduled_messages_overlay_ui = mock_esm("../src/scheduled_messages_overlay_ui");
|
||||||
|
@ -180,6 +182,29 @@ run_test("alert_words", ({override}) => {
|
||||||
assert.ok(alert_words.has_alert_word("lunch"));
|
assert.ok(alert_words.has_alert_word("lunch"));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
run_test("saved_snippets", ({override}) => {
|
||||||
|
const add_event = event_fixtures.saved_snippets__add;
|
||||||
|
override(saved_snippets_ui, "rerender_dropdown_widget", noop);
|
||||||
|
{
|
||||||
|
const stub = make_stub();
|
||||||
|
override(saved_snippets, "add_saved_snippet", stub.f);
|
||||||
|
|
||||||
|
dispatch(add_event);
|
||||||
|
assert.equal(stub.num_calls, 1);
|
||||||
|
assert_same(stub.get_args("event").event, add_event.saved_snippet);
|
||||||
|
}
|
||||||
|
|
||||||
|
const remove_event = event_fixtures.saved_snippets__remove;
|
||||||
|
{
|
||||||
|
const stub = make_stub();
|
||||||
|
override(saved_snippets, "remove_saved_snippet", stub.f);
|
||||||
|
|
||||||
|
dispatch(remove_event);
|
||||||
|
assert.equal(stub.num_calls, 1);
|
||||||
|
assert_same(stub.get_args("event").event, remove_event.saved_snippet_id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
run_test("attachments", ({override}) => {
|
run_test("attachments", ({override}) => {
|
||||||
const event = event_fixtures.attachment__add;
|
const event = event_fixtures.attachment__add;
|
||||||
const stub = make_stub();
|
const stub = make_stub();
|
||||||
|
|
|
@ -596,6 +596,23 @@ exports.fixtures = {
|
||||||
server_generation: 1707511515,
|
server_generation: 1707511515,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
saved_snippets__add: {
|
||||||
|
type: "saved_snippets",
|
||||||
|
op: "add",
|
||||||
|
saved_snippet: {
|
||||||
|
id: 1,
|
||||||
|
title: "Example",
|
||||||
|
content: "Welcome to the organization.",
|
||||||
|
date_created: 1681662420,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
saved_snippets__remove: {
|
||||||
|
type: "saved_snippets",
|
||||||
|
op: "remove",
|
||||||
|
saved_snippet_id: 1,
|
||||||
|
},
|
||||||
|
|
||||||
scheduled_messages__add: {
|
scheduled_messages__add: {
|
||||||
type: "scheduled_messages",
|
type: "scheduled_messages",
|
||||||
op: "add",
|
op: "add",
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const {strict: assert} = require("assert");
|
||||||
|
|
||||||
|
const {$t} = require("./lib/i18n");
|
||||||
|
const {set_global, zrequire} = require("./lib/namespace");
|
||||||
|
const {run_test} = require("./lib/test");
|
||||||
|
const blueslip = require("./lib/zblueslip");
|
||||||
|
|
||||||
|
set_global("page_params", {
|
||||||
|
is_spectator: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
saved_snippets: [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
title: "Test saved snippet",
|
||||||
|
content: "Test content",
|
||||||
|
date_created: 128374878,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const people = zrequire("people");
|
||||||
|
const saved_snippets = zrequire("saved_snippets");
|
||||||
|
|
||||||
|
people.add_active_user({
|
||||||
|
email: "tester@zulip.com",
|
||||||
|
full_name: "Tester von Tester",
|
||||||
|
user_id: 42,
|
||||||
|
});
|
||||||
|
|
||||||
|
people.initialize_current_user(42);
|
||||||
|
|
||||||
|
saved_snippets.initialize(params);
|
||||||
|
|
||||||
|
run_test("add_saved_snippet", () => {
|
||||||
|
const saved_snippet = {
|
||||||
|
id: 2,
|
||||||
|
title: "New saved snippet",
|
||||||
|
content: "Test content",
|
||||||
|
date_created: 128374878,
|
||||||
|
};
|
||||||
|
saved_snippets.add_saved_snippet(saved_snippet);
|
||||||
|
|
||||||
|
const my_saved_snippet = saved_snippets.get_saved_snippet_by_id(2);
|
||||||
|
assert.equal(my_saved_snippet, saved_snippet);
|
||||||
|
});
|
||||||
|
|
||||||
|
run_test("options for dropdown widget", () => {
|
||||||
|
const saved_snippet = {
|
||||||
|
id: 3,
|
||||||
|
title: "Another saved snippet",
|
||||||
|
content: "Test content",
|
||||||
|
date_created: 128374876,
|
||||||
|
};
|
||||||
|
saved_snippets.add_saved_snippet(saved_snippet);
|
||||||
|
|
||||||
|
assert.deepEqual(saved_snippets.get_options_for_dropdown_widget(), [
|
||||||
|
{
|
||||||
|
unique_id: -1,
|
||||||
|
name: $t({defaultMessage: "Add a new saved snippet"}),
|
||||||
|
description: "",
|
||||||
|
bold_current_selection: true,
|
||||||
|
has_delete_icon: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
unique_id: 3,
|
||||||
|
name: "Another saved snippet",
|
||||||
|
description: "Test content",
|
||||||
|
bold_current_selection: true,
|
||||||
|
has_delete_icon: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
unique_id: 2,
|
||||||
|
name: "New saved snippet",
|
||||||
|
description: "Test content",
|
||||||
|
bold_current_selection: true,
|
||||||
|
has_delete_icon: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
unique_id: 1,
|
||||||
|
name: "Test saved snippet",
|
||||||
|
description: "Test content",
|
||||||
|
bold_current_selection: true,
|
||||||
|
has_delete_icon: true,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
run_test("remove_saved_snippet", () => {
|
||||||
|
const saved_snippet_id = params.saved_snippets[0].id;
|
||||||
|
saved_snippets.remove_saved_snippet(saved_snippet_id);
|
||||||
|
blueslip.expect("error", "Could not find saved snippet");
|
||||||
|
assert.equal(saved_snippets.get_saved_snippet_by_id(saved_snippet_id), undefined);
|
||||||
|
});
|
Loading…
Reference in New Issue