mirror of https://github.com/zulip/zulip.git
copy_and_paste: Convert module to TypeScript.
Signed-off-by: Anders Kaseorg <anders@zulip.com>
This commit is contained in:
parent
2e776bf8dc
commit
4d407c6b8d
|
@ -86,7 +86,7 @@ EXEMPT_FILES = make_set(
|
||||||
"web/src/condense.ts",
|
"web/src/condense.ts",
|
||||||
"web/src/confirm_dialog.ts",
|
"web/src/confirm_dialog.ts",
|
||||||
"web/src/copied_tooltip.ts",
|
"web/src/copied_tooltip.ts",
|
||||||
"web/src/copy_and_paste.js",
|
"web/src/copy_and_paste.ts",
|
||||||
"web/src/csrf.ts",
|
"web/src/csrf.ts",
|
||||||
"web/src/css_variables.d.ts",
|
"web/src/css_variables.d.ts",
|
||||||
"web/src/css_variables.js",
|
"web/src/css_variables.js",
|
||||||
|
|
|
@ -1,13 +1,25 @@
|
||||||
import isUrl from "is-url";
|
import isUrl from "is-url";
|
||||||
import $ from "jquery";
|
import $ from "jquery";
|
||||||
import _ from "lodash";
|
import _ from "lodash";
|
||||||
|
import assert from "minimalistic-assert";
|
||||||
import TurndownService from "turndown";
|
import TurndownService from "turndown";
|
||||||
|
|
||||||
import * as compose_ui from "./compose_ui";
|
import * as compose_ui from "./compose_ui";
|
||||||
import * as message_lists from "./message_lists";
|
import * as message_lists from "./message_lists";
|
||||||
import * as rows from "./rows";
|
import * as rows from "./rows";
|
||||||
|
|
||||||
function find_boundary_tr($initial_tr, iterate_row) {
|
declare global {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/consistent-type-definitions
|
||||||
|
interface HTMLElementTagNameMap {
|
||||||
|
math: HTMLElement;
|
||||||
|
strike: HTMLElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function find_boundary_tr(
|
||||||
|
$initial_tr: JQuery,
|
||||||
|
iterate_row: ($tr: JQuery) => JQuery,
|
||||||
|
): [number, boolean] | undefined {
|
||||||
let j;
|
let j;
|
||||||
let skip_same_td_check = false;
|
let skip_same_td_check = false;
|
||||||
let $tr = $initial_tr;
|
let $tr = $initial_tr;
|
||||||
|
@ -40,7 +52,7 @@ function find_boundary_tr($initial_tr, iterate_row) {
|
||||||
return [rows.id($tr), skip_same_td_check];
|
return [rows.id($tr), skip_same_td_check];
|
||||||
}
|
}
|
||||||
|
|
||||||
function construct_recipient_header($message_row) {
|
function construct_recipient_header($message_row: JQuery): JQuery {
|
||||||
const message_header_content = rows
|
const message_header_content = rows
|
||||||
.get_message_recipient_header($message_row)
|
.get_message_recipient_header($message_row)
|
||||||
.text()
|
.text()
|
||||||
|
@ -65,7 +77,7 @@ Do not be afraid to change this code if you understand
|
||||||
how modern browsers deal with copy/paste. Just test
|
how modern browsers deal with copy/paste. Just test
|
||||||
your changes carefully.
|
your changes carefully.
|
||||||
*/
|
*/
|
||||||
function construct_copy_div($div, start_id, end_id) {
|
function construct_copy_div($div: JQuery, start_id: number, end_id: number): void {
|
||||||
if (message_lists.current === undefined) {
|
if (message_lists.current === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -89,6 +101,7 @@ function construct_copy_div($div, start_id, end_id) {
|
||||||
should_include_start_recipient_header = true;
|
should_include_start_recipient_header = true;
|
||||||
}
|
}
|
||||||
const message = message_lists.current.get(rows.id($row));
|
const message = message_lists.current.get(rows.id($row));
|
||||||
|
assert(message !== undefined);
|
||||||
const $content = $(message.content);
|
const $content = $(message.content);
|
||||||
$content.first().prepend(
|
$content.first().prepend(
|
||||||
$("<span>")
|
$("<span>")
|
||||||
|
@ -103,7 +116,7 @@ function construct_copy_div($div, start_id, end_id) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function select_div($div, selection) {
|
function select_div($div: JQuery, selection: Selection): void {
|
||||||
$div.css({
|
$div.css({
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
left: "-99999px",
|
left: "-99999px",
|
||||||
|
@ -121,9 +134,10 @@ function select_div($div, selection) {
|
||||||
selection.selectAllChildren($div[0]);
|
selection.selectAllChildren($div[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function remove_div(_div, ranges, selection) {
|
function remove_div(_div: JQuery, ranges: Range[]): void {
|
||||||
window.setTimeout(() => {
|
window.setTimeout(() => {
|
||||||
selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
|
assert(selection !== null);
|
||||||
selection.removeAllRanges();
|
selection.removeAllRanges();
|
||||||
|
|
||||||
for (const range of ranges) {
|
for (const range of ranges) {
|
||||||
|
@ -134,7 +148,7 @@ function remove_div(_div, ranges, selection) {
|
||||||
}, 0);
|
}, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function copy_handler() {
|
export function copy_handler(): void {
|
||||||
// This is the main handler for copying message content via
|
// This is the main handler for copying message content via
|
||||||
// `Ctrl+C` in Zulip (note that this is totally independent of the
|
// `Ctrl+C` in Zulip (note that this is totally independent of the
|
||||||
// "select region" copy behavior on Linux; that is handled
|
// "select region" copy behavior on Linux; that is handled
|
||||||
|
@ -151,6 +165,7 @@ export function copy_handler() {
|
||||||
// were partially covered by the selection.
|
// were partially covered by the selection.
|
||||||
|
|
||||||
const selection = window.getSelection();
|
const selection = window.getSelection();
|
||||||
|
assert(selection !== null);
|
||||||
const analysis = analyze_selection(selection);
|
const analysis = analyze_selection(selection);
|
||||||
const ranges = analysis.ranges;
|
const ranges = analysis.ranges;
|
||||||
const start_id = analysis.start_id;
|
const start_id = analysis.start_id;
|
||||||
|
@ -189,10 +204,15 @@ export function copy_handler() {
|
||||||
// instead of copying the original selection
|
// instead of copying the original selection
|
||||||
select_div($div, selection);
|
select_div($div, selection);
|
||||||
document.execCommand("copy");
|
document.execCommand("copy");
|
||||||
remove_div($div, ranges, selection);
|
remove_div($div, ranges);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function analyze_selection(selection) {
|
export function analyze_selection(selection: Selection): {
|
||||||
|
ranges: Range[];
|
||||||
|
start_id: number | undefined;
|
||||||
|
end_id: number | undefined;
|
||||||
|
skip_same_td_check: boolean;
|
||||||
|
} {
|
||||||
// Here we analyze our selection to determine if part of a message
|
// Here we analyze our selection to determine if part of a message
|
||||||
// or multiple messages are selected.
|
// or multiple messages are selected.
|
||||||
//
|
//
|
||||||
|
@ -265,7 +285,7 @@ export function analyze_selection(selection) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_end_tr_from_endc($endc) {
|
function get_end_tr_from_endc($endc: JQuery<Node>): JQuery {
|
||||||
if ($endc.attr("id") === "bottom_whitespace" || $endc.attr("id") === "compose_close") {
|
if ($endc.attr("id") === "bottom_whitespace" || $endc.attr("id") === "compose_close") {
|
||||||
// If the selection ends in the bottom whitespace, we should
|
// If the selection ends in the bottom whitespace, we should
|
||||||
// act as though the selection ends on the final message.
|
// act as though the selection ends on the final message.
|
||||||
|
@ -293,7 +313,7 @@ function get_end_tr_from_endc($endc) {
|
||||||
// we can use the last message from the previous recipient_row.
|
// we can use the last message from the previous recipient_row.
|
||||||
if ($endc.parents(".message_header").length > 0) {
|
if ($endc.parents(".message_header").length > 0) {
|
||||||
const $overflow_recipient_row = $endc.parents(".recipient_row").first();
|
const $overflow_recipient_row = $endc.parents(".recipient_row").first();
|
||||||
return $overflow_recipient_row.prev(".recipient_row").last(".message_row");
|
return $overflow_recipient_row.prev(".recipient_row").last();
|
||||||
}
|
}
|
||||||
// If somehow we get here, do the default return.
|
// If somehow we get here, do the default return.
|
||||||
}
|
}
|
||||||
|
@ -301,45 +321,52 @@ function get_end_tr_from_endc($endc) {
|
||||||
return $endc.parents(".selectable_row").first();
|
return $endc.parents(".selectable_row").first();
|
||||||
}
|
}
|
||||||
|
|
||||||
function deduplicate_newlines(attribute) {
|
function deduplicate_newlines(attribute: string): string {
|
||||||
// We replace any occurrences of one or more consecutive newlines followed by
|
// We replace any occurrences of one or more consecutive newlines followed by
|
||||||
// zero or more whitespace characters with a single newline character.
|
// zero or more whitespace characters with a single newline character.
|
||||||
return attribute ? attribute.replaceAll(/(\n+\s*)+/g, "\n") : "";
|
return attribute ? attribute.replaceAll(/(\n+\s*)+/g, "\n") : "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function image_to_zulip_markdown(_content, node) {
|
function image_to_zulip_markdown(
|
||||||
|
_content: string,
|
||||||
|
node: Element | Document | DocumentFragment,
|
||||||
|
): string {
|
||||||
|
assert(node instanceof Element);
|
||||||
if (node.nodeName === "IMG" && node.classList.contains("emoji") && node.hasAttribute("alt")) {
|
if (node.nodeName === "IMG" && node.classList.contains("emoji") && node.hasAttribute("alt")) {
|
||||||
// For Zulip's custom emoji
|
// For Zulip's custom emoji
|
||||||
return node.getAttribute("alt");
|
return node.getAttribute("alt") ?? "";
|
||||||
}
|
}
|
||||||
const src = node.getAttribute("src") || node.getAttribute("href") || "";
|
const src = node.getAttribute("src") ?? node.getAttribute("href") ?? "";
|
||||||
const title = deduplicate_newlines(node.getAttribute("title")) || "";
|
const title = deduplicate_newlines(node.getAttribute("title") ?? "");
|
||||||
// Using Zulip's link like syntax for images
|
// Using Zulip's link like syntax for images
|
||||||
return src ? "[" + title + "](" + src + ")" : node.getAttribute("alt") || "";
|
return src ? "[" + title + "](" + src + ")" : node.getAttribute("alt") ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
function within_single_element(html_fragment) {
|
function within_single_element(html_fragment: HTMLElement): boolean {
|
||||||
return (
|
return (
|
||||||
html_fragment.childNodes.length === 1 &&
|
html_fragment.childNodes.length === 1 &&
|
||||||
html_fragment.firstElementChild &&
|
html_fragment.firstElementChild !== null &&
|
||||||
html_fragment.firstElementChild.innerHTML
|
html_fragment.firstElementChild.innerHTML !== ""
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function is_white_space_pre(paste_html) {
|
export function is_white_space_pre(paste_html: string): boolean {
|
||||||
const html_fragment = new DOMParser()
|
const html_fragment = new DOMParser()
|
||||||
.parseFromString(paste_html, "text/html")
|
.parseFromString(paste_html, "text/html")
|
||||||
.querySelector("body");
|
.querySelector("body");
|
||||||
|
assert(html_fragment !== null);
|
||||||
return (
|
return (
|
||||||
within_single_element(html_fragment) &&
|
within_single_element(html_fragment) &&
|
||||||
|
html_fragment.firstElementChild instanceof HTMLElement &&
|
||||||
html_fragment.firstElementChild.style.whiteSpace === "pre"
|
html_fragment.firstElementChild.style.whiteSpace === "pre"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function paste_handler_converter(paste_html) {
|
export function paste_handler_converter(paste_html: string): string {
|
||||||
const copied_html_fragment = new DOMParser()
|
const copied_html_fragment = new DOMParser()
|
||||||
.parseFromString(paste_html, "text/html")
|
.parseFromString(paste_html, "text/html")
|
||||||
.querySelector("body");
|
.querySelector("body");
|
||||||
|
assert(copied_html_fragment !== null);
|
||||||
const copied_within_single_element = within_single_element(copied_html_fragment);
|
const copied_within_single_element = within_single_element(copied_html_fragment);
|
||||||
const outer_elements_to_retain = ["PRE", "UL", "OL", "A", "CODE"];
|
const outer_elements_to_retain = ["PRE", "UL", "OL", "A", "CODE"];
|
||||||
// If the entire selection copied is within a single HTML element (like an
|
// If the entire selection copied is within a single HTML element (like an
|
||||||
|
@ -347,9 +374,10 @@ export function paste_handler_converter(paste_html) {
|
||||||
// identify the intended structure of the copied content.
|
// identify the intended structure of the copied content.
|
||||||
if (
|
if (
|
||||||
copied_within_single_element &&
|
copied_within_single_element &&
|
||||||
|
copied_html_fragment.firstElementChild !== null &&
|
||||||
!outer_elements_to_retain.includes(copied_html_fragment.firstElementChild.nodeName)
|
!outer_elements_to_retain.includes(copied_html_fragment.firstElementChild.nodeName)
|
||||||
) {
|
) {
|
||||||
paste_html = copied_html_fragment.firstChild.innerHTML;
|
paste_html = copied_html_fragment.firstElementChild.innerHTML;
|
||||||
}
|
}
|
||||||
|
|
||||||
// turning off escaping (for now) to remove extra `/`
|
// turning off escaping (for now) to remove extra `/`
|
||||||
|
@ -376,11 +404,12 @@ export function paste_handler_converter(paste_html) {
|
||||||
turndownService.addRule("links", {
|
turndownService.addRule("links", {
|
||||||
filter: ["a"],
|
filter: ["a"],
|
||||||
replacement(content, node) {
|
replacement(content, node) {
|
||||||
|
assert(node instanceof HTMLAnchorElement);
|
||||||
if (node.href === content) {
|
if (node.href === content) {
|
||||||
// Checks for raw links without custom text.
|
// Checks for raw links without custom text.
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
if (node.childNodes.length === 1 && node.firstChild.nodeName === "IMG") {
|
if (node.childNodes.length === 1 && node.firstChild!.nodeName === "IMG") {
|
||||||
// ignore link's url if it only has an image
|
// ignore link's url if it only has an image
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
@ -398,20 +427,21 @@ export function paste_handler_converter(paste_html) {
|
||||||
.replace(/\n+$/, "\n") // replace trailing newlines with just a single one
|
.replace(/\n+$/, "\n") // replace trailing newlines with just a single one
|
||||||
.replaceAll(/\n/gm, "\n "); // custom 2 space indent
|
.replaceAll(/\n/gm, "\n "); // custom 2 space indent
|
||||||
let prefix = "* ";
|
let prefix = "* ";
|
||||||
const parent = node.parentNode;
|
const parent = node.parentElement;
|
||||||
|
assert(parent !== null);
|
||||||
if (parent.nodeName === "OL") {
|
if (parent.nodeName === "OL") {
|
||||||
const start = parent.getAttribute("start");
|
const start = parent.getAttribute("start");
|
||||||
const index = Array.prototype.indexOf.call(parent.children, node);
|
const index = Array.prototype.indexOf.call(parent.children, node);
|
||||||
prefix = (start ? Number(start) + index : index + 1) + ". ";
|
prefix = (start ? Number(start) + index : index + 1) + ". ";
|
||||||
}
|
}
|
||||||
return prefix + content + (node.nextSibling && !/\n$/.test(content) ? "\n" : "");
|
return prefix + content + (node.nextSibling && !content.endsWith("\n") ? "\n" : "");
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
turndownService.addRule("zulipImagePreview", {
|
turndownService.addRule("zulipImagePreview", {
|
||||||
filter(node) {
|
filter(node) {
|
||||||
// select image previews in Zulip messages
|
// select image previews in Zulip messages
|
||||||
return (
|
return (
|
||||||
node.classList.contains("message_inline_image") && node.firstChild.nodeName === "A"
|
node.classList.contains("message_inline_image") && node.firstChild?.nodeName === "A"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -423,18 +453,19 @@ export function paste_handler_converter(paste_html) {
|
||||||
// does not have the `message_inline_image` class, it means it is the generating
|
// does not have the `message_inline_image` class, it means it is the generating
|
||||||
// link, and not the preview, meaning the generating link is copied as well.
|
// link, and not the preview, meaning the generating link is copied as well.
|
||||||
const copied_html = new DOMParser().parseFromString(paste_html, "text/html");
|
const copied_html = new DOMParser().parseFromString(paste_html, "text/html");
|
||||||
|
let href;
|
||||||
if (
|
if (
|
||||||
|
node.firstElementChild === null ||
|
||||||
|
(href = node.firstElementChild.getAttribute("href")) === null ||
|
||||||
!copied_html
|
!copied_html
|
||||||
.querySelector(
|
.querySelector("a[href='" + CSS.escape(href) + "']")
|
||||||
"a[href='" + CSS.escape(node.firstChild.getAttribute("href")) + "']",
|
?.parentElement?.classList.contains("message_inline_image")
|
||||||
)
|
|
||||||
?.parentNode?.classList.contains("message_inline_image")
|
|
||||||
) {
|
) {
|
||||||
// We skip previews which have their generating link copied too, to avoid
|
// We skip previews which have their generating link copied too, to avoid
|
||||||
// double pasting the same link.
|
// double pasting the same link.
|
||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
return image_to_zulip_markdown(content, node.firstChild);
|
return image_to_zulip_markdown(content, node.firstElementChild);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
turndownService.addRule("images", {
|
turndownService.addRule("images", {
|
||||||
|
@ -465,19 +496,23 @@ export function paste_handler_converter(paste_html) {
|
||||||
// Everything else works the same.
|
// Everything else works the same.
|
||||||
turndownService.addRule("fencedCodeBlock", {
|
turndownService.addRule("fencedCodeBlock", {
|
||||||
filter(node, options) {
|
filter(node, options) {
|
||||||
|
let text_children;
|
||||||
return (
|
return (
|
||||||
options.codeBlockStyle === "fenced" &&
|
options.codeBlockStyle === "fenced" &&
|
||||||
node.nodeName === "PRE" &&
|
node.nodeName === "PRE" &&
|
||||||
[...node.childNodes].filter((child) => child.textContent.trim() !== "").length ===
|
(text_children = [...node.childNodes].filter(
|
||||||
1 &&
|
(child) => child.textContent !== null && child.textContent.trim() !== "",
|
||||||
[...node.childNodes].find((child) => child.textContent.trim() !== "").nodeName ===
|
)).length === 1 &&
|
||||||
"CODE"
|
text_children[0].nodeName === "CODE"
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
replacement(_content, node, options) {
|
replacement(_content, node, options) {
|
||||||
const codeElement = [...node.childNodes].find((child) => child.nodeName === "CODE");
|
assert(node instanceof HTMLElement);
|
||||||
|
const codeElement = [...node.children].find((child) => child.nodeName === "CODE");
|
||||||
|
assert(codeElement !== undefined);
|
||||||
const code = codeElement.textContent;
|
const code = codeElement.textContent;
|
||||||
|
assert(code !== null);
|
||||||
|
|
||||||
// We convert single line code inside a code block to inline markdown code,
|
// We convert single line code inside a code block to inline markdown code,
|
||||||
// and the code for this is taken from upstream's `code` rule.
|
// and the code for this is taken from upstream's `code` rule.
|
||||||
|
@ -490,7 +525,7 @@ export function paste_handler_converter(paste_html) {
|
||||||
// Pick the shortest sequence of backticks that is not found in the code
|
// Pick the shortest sequence of backticks that is not found in the code
|
||||||
// to be the delimiter.
|
// to be the delimiter.
|
||||||
let delimiter = "`";
|
let delimiter = "`";
|
||||||
const matches = code.match(/`+/gm) || [];
|
const matches: string[] = code.match(/`+/gm) ?? [];
|
||||||
while (matches.includes(delimiter)) {
|
while (matches.includes(delimiter)) {
|
||||||
delimiter = delimiter + "`";
|
delimiter = delimiter + "`";
|
||||||
}
|
}
|
||||||
|
@ -498,11 +533,12 @@ export function paste_handler_converter(paste_html) {
|
||||||
return delimiter + extraSpace + code + extraSpace + delimiter;
|
return delimiter + extraSpace + code + extraSpace + delimiter;
|
||||||
}
|
}
|
||||||
|
|
||||||
const className = codeElement.getAttribute("class") || "";
|
const className = codeElement.getAttribute("class") ?? "";
|
||||||
const language = node.parentElement?.classList.contains("zulip-code-block")
|
const language = node.parentElement?.classList.contains("zulip-code-block")
|
||||||
? node.closest(".codehilite")?.dataset?.codeLanguage || ""
|
? node.closest<HTMLElement>(".codehilite")?.dataset?.codeLanguage ?? ""
|
||||||
: (className.match(/language-(\S+)/) || [null, ""])[1];
|
: (className.match(/language-(\S+)/) ?? [null, ""])[1];
|
||||||
|
|
||||||
|
assert(options.fence !== undefined);
|
||||||
const fenceChar = options.fence.charAt(0);
|
const fenceChar = options.fence.charAt(0);
|
||||||
let fenceSize = 3;
|
let fenceSize = 3;
|
||||||
const fenceInCodeRegex = new RegExp("^" + fenceChar + "{3,}", "gm");
|
const fenceInCodeRegex = new RegExp("^" + fenceChar + "{3,}", "gm");
|
||||||
|
@ -532,7 +568,7 @@ export function paste_handler_converter(paste_html) {
|
||||||
return markdown_text;
|
return markdown_text;
|
||||||
}
|
}
|
||||||
|
|
||||||
function is_safe_url_paste_target($textarea) {
|
function is_safe_url_paste_target($textarea: JQuery<HTMLTextAreaElement>): boolean {
|
||||||
const range = $textarea.range();
|
const range = $textarea.range();
|
||||||
|
|
||||||
if (!range.text) {
|
if (!range.text) {
|
||||||
|
@ -563,7 +599,7 @@ function is_safe_url_paste_target($textarea) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function maybe_transform_html(html, text) {
|
export function maybe_transform_html(html: string, text: string): string {
|
||||||
if (is_white_space_pre(html)) {
|
if (is_white_space_pre(html)) {
|
||||||
// Copied content styled with `white-space: pre` is pasted as is
|
// Copied content styled with `white-space: pre` is pasted as is
|
||||||
// but formatted as code. We need this for content copied from
|
// but formatted as code. We need this for content copied from
|
||||||
|
@ -573,7 +609,8 @@ export function maybe_transform_html(html, text) {
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function paste_handler(event) {
|
export function paste_handler(this: HTMLTextAreaElement, event: JQuery.TriggeredEvent): void {
|
||||||
|
assert(event.originalEvent instanceof ClipboardEvent);
|
||||||
const clipboardData = event.originalEvent.clipboardData;
|
const clipboardData = event.originalEvent.clipboardData;
|
||||||
if (!clipboardData) {
|
if (!clipboardData) {
|
||||||
// On IE11, ClipboardData isn't defined. One can instead
|
// On IE11, ClipboardData isn't defined. One can instead
|
||||||
|
@ -585,7 +622,7 @@ export function paste_handler(event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clipboardData.getData) {
|
if (clipboardData.getData) {
|
||||||
const $textarea = $(event.currentTarget);
|
const $textarea = $(this);
|
||||||
const paste_text = clipboardData.getData("text");
|
const paste_text = clipboardData.getData("text");
|
||||||
let paste_html = clipboardData.getData("text/html");
|
let paste_html = clipboardData.getData("text/html");
|
||||||
// Trim the paste_text to accommodate sloppy copying
|
// Trim the paste_text to accommodate sloppy copying
|
||||||
|
@ -619,7 +656,7 @@ export function paste_handler(event) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function initialize() {
|
export function initialize(): void {
|
||||||
$("textarea#compose-textarea").on("paste", paste_handler);
|
$<HTMLTextAreaElement>("textarea#compose-textarea").on("paste", paste_handler);
|
||||||
$("body").on("paste", ".message_edit_content", paste_handler);
|
$("body").on("paste", "textarea.message_edit_content", paste_handler);
|
||||||
}
|
}
|
|
@ -274,7 +274,7 @@ export const update_elements = ($content: JQuery): void => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// Display the view-code-in-playground and the copy-to-clipboard button inside the div.codehilite element,
|
// Display the view-code-in-playground and the copy-to-clipboard button inside the div.codehilite element,
|
||||||
// and add a `zulip-code-block` class to it to detect it easily in `copy_and_paste.js`.
|
// and add a `zulip-code-block` class to it to detect it easily in `copy_and_paste.ts`.
|
||||||
$content.find("div.codehilite").each(function (): void {
|
$content.find("div.codehilite").each(function (): void {
|
||||||
const $codehilite = $(this);
|
const $codehilite = $(this);
|
||||||
const $pre = $codehilite.find("pre");
|
const $pre = $codehilite.find("pre");
|
||||||
|
|
|
@ -22,6 +22,8 @@ process.env.NODE_ENV = "test";
|
||||||
|
|
||||||
const dom = new JSDOM("", {url: "http://zulip.zulipdev.com/"});
|
const dom = new JSDOM("", {url: "http://zulip.zulipdev.com/"});
|
||||||
global.DOMParser = dom.window.DOMParser;
|
global.DOMParser = dom.window.DOMParser;
|
||||||
|
global.HTMLAnchorElement = dom.window.HTMLAnchorElement;
|
||||||
|
global.HTMLElement = dom.window.HTMLElement;
|
||||||
global.navigator = {
|
global.navigator = {
|
||||||
userAgent: "node.js",
|
userAgent: "node.js",
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue