popovers: Add Copied! tooltip when `copy code` button is clicked.

`copy code` button now show a `Copied!` tooltip when clicked.
It implements a similar function used on `saved as draft` notice.

We need to modify the copy_code_button template to limit
data-tippy-trigger to not include click; otherwise, repeated clicks to
copy code will incorrectly also display the "Copy code" tooltip
alternating with "Copied".

Fixes part of #21036.
This commit is contained in:
Deekshith S Shetty 2023-02-21 21:15:12 +05:30 committed by Tim Abbott
parent f84de2212e
commit 596abf6190
4 changed files with 35 additions and 2 deletions

View File

@ -13,6 +13,7 @@ import * as realm_playground from "./realm_playground";
import * as rtl from "./rtl"; import * as rtl from "./rtl";
import * as stream_data from "./stream_data"; import * as stream_data from "./stream_data";
import * as timerender from "./timerender"; import * as timerender from "./timerender";
import {show_copied_confirmation} from "./tippyjs";
import * as user_groups from "./user_groups"; import * as user_groups from "./user_groups";
import {user_settings} from "./user_settings"; import {user_settings} from "./user_settings";
@ -227,11 +228,14 @@ export const update_elements = ($content) => {
} }
const $copy_button = $(copy_code_button()); const $copy_button = $(copy_code_button());
$pre.prepend($copy_button); $pre.prepend($copy_button);
new ClipboardJS($copy_button[0], { const clipboard = new ClipboardJS($copy_button[0], {
text(copy_element) { text(copy_element) {
return $(copy_element).siblings("code").text(); return $(copy_element).siblings("code").text();
}, },
}); });
clipboard.on("success", () => {
show_copied_confirmation($copy_button[0]);
});
}); });
// Display emoji (including realm emoji) as text if // Display emoji (including realm emoji) as text if

View File

@ -624,3 +624,22 @@ export function initialize() {
appendTo: () => document.body, appendTo: () => document.body,
}); });
} }
export function show_copied_confirmation($copy_button) {
// Display a tooltip to notify the user the message or code was copied.
const instance = tippy($copy_button, {
placement: "top",
appendTo: () => document.body,
onUntrigger() {
remove_instance();
},
});
instance.setContent($t({defaultMessage: "Copied!"}));
instance.show();
function remove_instance() {
if (!instance.state.isDestroyed) {
instance.destroy();
}
}
setTimeout(remove_instance, 1000);
}

View File

@ -1,3 +1,3 @@
<button class="btn pull-left copy_button_base copy_codeblock" data-tippy-content="{{t 'Copy code' }}" aria-label="{{t 'Copy code' }}"> <button class="btn pull-left copy_button_base copy_codeblock" data-tippy-content="{{t 'Copy code' }}" data-tippy-trigger="mouseenter" aria-label="{{t 'Copy code' }}">
{{> copy_to_clipboard_svg }} {{> copy_to_clipboard_svg }}
</button> </button>

View File

@ -13,11 +13,15 @@ class Clipboard {
constructor(...args) { constructor(...args) {
clipboard_args = args; clipboard_args = args;
} }
on(success, show_copied_confirmation) {
show_copied_confirmation();
}
} }
mock_cjs("clipboard", Clipboard); mock_cjs("clipboard", Clipboard);
const realm_playground = mock_esm("../src/realm_playground"); const realm_playground = mock_esm("../src/realm_playground");
const tippyjs = mock_esm("../src/tippyjs");
user_settings.emojiset = "apple"; user_settings.emojiset = "apple";
const rm = zrequire("rendered_markdown"); const rm = zrequire("rendered_markdown");
@ -428,6 +432,8 @@ run_test("code playground none", ({override, mock_template}) => {
return undefined; return undefined;
}); });
override(tippyjs, "show_copied_confirmation", () => {});
const {prepends, $copy_code, $view_code} = test_code_playground(mock_template, false); const {prepends, $copy_code, $view_code} = test_code_playground(mock_template, false);
assert.deepEqual(prepends, [$copy_code]); assert.deepEqual(prepends, [$copy_code]);
assert_clipboard_setup(); assert_clipboard_setup();
@ -442,6 +448,8 @@ run_test("code playground single", ({override, mock_template}) => {
return [{name: "Some Javascript Playground"}]; return [{name: "Some Javascript Playground"}];
}); });
override(tippyjs, "show_copied_confirmation", () => {});
const {prepends, $copy_code, $view_code} = test_code_playground(mock_template, true); const {prepends, $copy_code, $view_code} = test_code_playground(mock_template, true);
assert.deepEqual(prepends, [$view_code, $copy_code]); assert.deepEqual(prepends, [$view_code, $copy_code]);
assert_clipboard_setup(); assert_clipboard_setup();
@ -460,6 +468,8 @@ run_test("code playground multiple", ({override, mock_template}) => {
return ["whatever", "whatever"]; return ["whatever", "whatever"];
}); });
override(tippyjs, "show_copied_confirmation", () => {});
const {prepends, $copy_code, $view_code} = test_code_playground(mock_template, true); const {prepends, $copy_code, $view_code} = test_code_playground(mock_template, true);
assert.deepEqual(prepends, [$view_code, $copy_code]); assert.deepEqual(prepends, [$view_code, $copy_code]);
assert_clipboard_setup(); assert_clipboard_setup();