diff --git a/package.json b/package.json index 103ba0eadc..b56b4899d0 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "handlebars-loader": "^1.7.1", "html-webpack-plugin": "^5.3.2", "intl-messageformat": "^10.3.0", + "is-url": "^1.2.4", "jquery": "^3.6.3", "jquery-caret-plugin": "^1.5.2", "jquery-validation": "^1.19.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 03467ce0a9..438134d4e2 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -134,6 +134,9 @@ dependencies: intl-messageformat: specifier: ^10.3.0 version: 10.5.0 + is-url: + specifier: ^1.2.4 + version: 1.2.4 jquery: specifier: ^3.6.3 version: 3.7.0 @@ -7455,6 +7458,10 @@ packages: unc-path-regex: 0.1.2 dev: false + /is-url@1.2.4: + resolution: {integrity: sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==} + dev: false + /is-utf8@0.2.1: resolution: {integrity: sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==} dev: false diff --git a/web/src/compose_ui.js b/web/src/compose_ui.js index 8acc0d3630..151aab6685 100644 --- a/web/src/compose_ui.js +++ b/web/src/compose_ui.js @@ -343,7 +343,7 @@ export function handle_keyup(_event, $textarea) { rtl.set_rtl_class_for_textarea($textarea); } -export function format_text($textarea, type) { +export function format_text($textarea, type, inserted_content) { const italic_syntax = "*"; const bold_syntax = "**"; const bold_and_italic_syntax = "***"; @@ -507,6 +507,14 @@ export function format_text($textarea, type) { field.setSelectionRange(new_start, new_end); break; } + case "linked": { + // From a paste event with a URL as inserted content + wrapSelection(field, "[", `](${inserted_content})`); + // Put the cursor at the end of the selection range + // and all wrapped material + $textarea.caret(range.end + `[](${inserted_content})`.length); + break; + } } } diff --git a/web/src/copy_and_paste.js b/web/src/copy_and_paste.js index 11400708fb..6a7d7d6ca9 100644 --- a/web/src/copy_and_paste.js +++ b/web/src/copy_and_paste.js @@ -1,3 +1,4 @@ +import isUrl from "is-url"; import $ from "jquery"; import TurndownService from "turndown"; @@ -341,7 +342,25 @@ export function paste_handler(event) { } if (clipboardData.getData) { + const $textarea = $(event.currentTarget); + const paste_text = clipboardData.getData("text"); const paste_html = clipboardData.getData("text/html"); + // Trim the paste_text to accommodate sloppy copying + const trimmed_paste_text = paste_text.trim(); + const range = $textarea.range(); + + // Only try to generate formatted links when dealing with a URL + // and a range selection. Note that even clipboards with "text/html" + // have a "text" equivalent, so we need an if statement that checks + // for more than a value on `trimmed_paste_text` + if (isUrl(trimmed_paste_text) && range.text) { + event.preventDefault(); + event.stopPropagation(); + const url = trimmed_paste_text; + compose_ui.format_text($textarea, "linked", url); + return; + } + if (paste_html && page_params.development_environment) { const text = paste_handler_converter(paste_html); const mdImageRegex = /^!\[.*]\(.*\)$/; @@ -359,5 +378,5 @@ export function paste_handler(event) { export function initialize() { $("#compose-textarea").on("paste", paste_handler); - $("body").on("paste", ".message_edit_form", paste_handler); + $("body").on("paste", ".message_edit_content", paste_handler); }