From 2a2c8c90f545ab0499500f051eabe1bdaf897517 Mon Sep 17 00:00:00 2001 From: Lukas Trombach Date: Tue, 19 Sep 2023 23:09:00 +1200 Subject: [PATCH 1/4] handle url paste --- .../components/common/markdown-textarea.tsx | 49 ++++++++++++++++++- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/src/shared/components/common/markdown-textarea.tsx b/src/shared/components/common/markdown-textarea.tsx index d122b4729..a09682f00 100644 --- a/src/shared/components/common/markdown-textarea.tsx +++ b/src/shared/components/common/markdown-textarea.tsx @@ -233,7 +233,7 @@ export class MarkdownTextArea extends Component< )} value={this.state.content} onInput={linkEvent(this, this.handleContentChange)} - onPaste={linkEvent(this, this.handleImageUploadPaste)} + onPaste={linkEvent(this, this.handlePaste)} onKeyDown={linkEvent(this, this.handleKeyBinds)} required disabled={this.isDisabled} @@ -374,10 +374,55 @@ export class MarkdownTextArea extends Component< autosize.update(textarea); } - handleImageUploadPaste(i: MarkdownTextArea, event: any) { + handlePaste(i: MarkdownTextArea, event: ClipboardEvent) { + if (!event.clipboardData) return; + + // check clipboard files const image = event.clipboardData.files[0]; if (image) { i.handleImageUpload(i, image); + return; + } + + // check clipboard url + const url = event.clipboardData.getData("text"); + if (i.isValidUrl(url)) { + i.handleUrlPaste(url, i, event); + return; + } + } + + handleUrlPaste(url: string, i: MarkdownTextArea, event: ClipboardEvent) { + // query textarea element + const textarea = document.getElementById(i.id); + + if (textarea instanceof HTMLTextAreaElement) { + event.preventDefault(); + const { selectionStart, selectionEnd } = textarea; + + // update textarea content + i.setState({ + content: `${ + i.state.content ? i.state.content.substring(0, selectionStart) : "" + }[${i.getSelectedText()}](${url})${ + i.state.content ? i.state.content.substring(selectionEnd) : "" + }`, + }); + i.contentChange(); + + // shift selection 1 to the right + textarea.setSelectionRange(selectionStart + 1, selectionEnd + 1); + } + } + + isValidUrl(value: string): boolean { + if (!value) return false; + + try { + new URL(value); + return true; + } catch { + return false; } } From ce7949dc4508c17599e4567279c53a5bf02e7eeb Mon Sep 17 00:00:00 2001 From: Lukas Trombach Date: Sun, 24 Sep 2023 17:24:18 +1300 Subject: [PATCH 2/4] change selection range after paste --- .../components/common/markdown-textarea.tsx | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/shared/components/common/markdown-textarea.tsx b/src/shared/components/common/markdown-textarea.tsx index a09682f00..a0384a5eb 100644 --- a/src/shared/components/common/markdown-textarea.tsx +++ b/src/shared/components/common/markdown-textarea.tsx @@ -385,42 +385,46 @@ export class MarkdownTextArea extends Component< } // check clipboard url - const url = event.clipboardData.getData("text"); - if (i.isValidUrl(url)) { + const url = i.isValidUrl(event.clipboardData.getData("text")); + if (url) { i.handleUrlPaste(url, i, event); return; } } - handleUrlPaste(url: string, i: MarkdownTextArea, event: ClipboardEvent) { + handleUrlPaste(url: URL, i: MarkdownTextArea, event: ClipboardEvent) { // query textarea element const textarea = document.getElementById(i.id); if (textarea instanceof HTMLTextAreaElement) { event.preventDefault(); const { selectionStart, selectionEnd } = textarea; + const selectedText = i.getSelectedText() || url.toString(); // update textarea content i.setState({ content: `${ i.state.content ? i.state.content.substring(0, selectionStart) : "" - }[${i.getSelectedText()}](${url})${ + }[${selectedText}](${url.toString()})${ i.state.content ? i.state.content.substring(selectionEnd) : "" }`, }); i.contentChange(); // shift selection 1 to the right - textarea.setSelectionRange(selectionStart + 1, selectionEnd + 1); + textarea.setSelectionRange( + selectionStart + 1, + selectionStart + 1 + selectedText.length, + ); } } - isValidUrl(value: string): boolean { + isValidUrl(value: string) { if (!value) return false; try { - new URL(value); - return true; + const url = new URL(value); + return url; } catch { return false; } From 6eb65336f2b879b609f1184940e6525defbba195 Mon Sep 17 00:00:00 2001 From: Lukas Trombach Date: Tue, 26 Sep 2023 22:02:05 +1300 Subject: [PATCH 3/4] use default paste behaviour if no text is selected --- .../components/common/markdown-textarea.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/shared/components/common/markdown-textarea.tsx b/src/shared/components/common/markdown-textarea.tsx index a0384a5eb..27fbb1e93 100644 --- a/src/shared/components/common/markdown-textarea.tsx +++ b/src/shared/components/common/markdown-textarea.tsx @@ -397,18 +397,22 @@ export class MarkdownTextArea extends Component< const textarea = document.getElementById(i.id); if (textarea instanceof HTMLTextAreaElement) { - event.preventDefault(); const { selectionStart, selectionEnd } = textarea; - const selectedText = i.getSelectedText() || url.toString(); + + // if no selection, just insert url + if (selectionStart === selectionEnd) return; + + event.preventDefault(); + const selectedText = i.getSelectedText(); // update textarea content - i.setState({ + i.setState(({ content }) => ({ content: `${ - i.state.content ? i.state.content.substring(0, selectionStart) : "" + content?.substring(0, selectionStart) ?? "" }[${selectedText}](${url.toString()})${ - i.state.content ? i.state.content.substring(selectionEnd) : "" + content?.substring(selectionEnd) ?? "" }`, - }); + })); i.contentChange(); // shift selection 1 to the right From de5dfb7fcd89856c113eb82f1eaf86654bee3d4a Mon Sep 17 00:00:00 2001 From: Lukas Trombach Date: Thu, 28 Sep 2023 23:09:08 +1300 Subject: [PATCH 4/4] change to `validUrl` helper function --- .../components/common/markdown-textarea.tsx | 23 ++++--------------- 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/src/shared/components/common/markdown-textarea.tsx b/src/shared/components/common/markdown-textarea.tsx index 27fbb1e93..41f97938c 100644 --- a/src/shared/components/common/markdown-textarea.tsx +++ b/src/shared/components/common/markdown-textarea.tsx @@ -21,6 +21,7 @@ import { EmojiPicker } from "./emoji-picker"; import { Icon, Spinner } from "./icon"; import { LanguageSelect } from "./language-select"; import ProgressBar from "./progress-bar"; +import validUrl from "@utils/helpers/valid-url"; interface MarkdownTextAreaProps { /** * Initial content inside the textarea @@ -385,14 +386,13 @@ export class MarkdownTextArea extends Component< } // check clipboard url - const url = i.isValidUrl(event.clipboardData.getData("text")); - if (url) { + const url = event.clipboardData.getData("text"); + if (validUrl(url)) { i.handleUrlPaste(url, i, event); - return; } } - handleUrlPaste(url: URL, i: MarkdownTextArea, event: ClipboardEvent) { + handleUrlPaste(url: string, i: MarkdownTextArea, event: ClipboardEvent) { // query textarea element const textarea = document.getElementById(i.id); @@ -409,9 +409,7 @@ export class MarkdownTextArea extends Component< i.setState(({ content }) => ({ content: `${ content?.substring(0, selectionStart) ?? "" - }[${selectedText}](${url.toString()})${ - content?.substring(selectionEnd) ?? "" - }`, + }[${selectedText}](${url})${content?.substring(selectionEnd) ?? ""}`, })); i.contentChange(); @@ -423,17 +421,6 @@ export class MarkdownTextArea extends Component< } } - isValidUrl(value: string) { - if (!value) return false; - - try { - const url = new URL(value); - return url; - } catch { - return false; - } - } - handleImageUpload(i: MarkdownTextArea, event: any) { const files: File[] = []; if (event.target) {