From 87dfeb13afacff95c0ff8054fb5ff2651892a001 Mon Sep 17 00:00:00 2001 From: Maxim I <5054326@gmail.com> Date: Tue, 17 Jun 2025 02:57:49 +0300 Subject: [PATCH 1/4] fix: prevent double markdown link brackets when pasting URL When adding a link using the "Add a link" button in comment editor, pasting a URL resulted in incorrect Markdown formatting (double brackets) instead of replacing the placeholder text. This fix adds a context check to prevent creating a new markdown link when we're already inside an existing one. Fixes #34740 --- web_src/js/features/comp/EditorUpload.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web_src/js/features/comp/EditorUpload.ts b/web_src/js/features/comp/EditorUpload.ts index f6d573142268c..9ccb2e0bc2d90 100644 --- a/web_src/js/features/comp/EditorUpload.ts +++ b/web_src/js/features/comp/EditorUpload.ts @@ -126,7 +126,10 @@ function handleClipboardText(textarea: HTMLTextAreaElement, e: ClipboardEvent, t const {value, selectionStart, selectionEnd} = textarea; const selectedText = value.substring(selectionStart, selectionEnd); const trimmedText = text.trim(); - if (selectedText && isUrl(trimmedText) && !isUrl(selectedText)) { + const beforeSelection = value.substring(0, selectionStart); + const afterSelection = value.substring(selectionEnd); + const isInMarkdownLink = beforeSelection.endsWith('](') && afterSelection.startsWith(')'); + if (selectedText && isUrl(trimmedText) && !isUrl(selectedText) && !isInMarkdownLink) { e.preventDefault(); replaceTextareaSelection(textarea, `[${selectedText}](${trimmedText})`); } From 9d723a8bcaad1c6552b9e41f8be40368ce19ff7b Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 17 Jun 2025 19:48:14 +0800 Subject: [PATCH 2/4] make it testable and add tests --- web_src/js/features/comp/EditorUpload.test.ts | 11 +++++++++- web_src/js/features/comp/EditorUpload.ts | 22 ++++++++++++------- 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/web_src/js/features/comp/EditorUpload.test.ts b/web_src/js/features/comp/EditorUpload.test.ts index 55f3f743893ec..4f5377ee1c382 100644 --- a/web_src/js/features/comp/EditorUpload.test.ts +++ b/web_src/js/features/comp/EditorUpload.test.ts @@ -1,4 +1,4 @@ -import {removeAttachmentLinksFromMarkdown} from './EditorUpload.ts'; +import {pasteAsMarkdownLink, removeAttachmentLinksFromMarkdown} from './EditorUpload.ts'; test('removeAttachmentLinksFromMarkdown', () => { expect(removeAttachmentLinksFromMarkdown('a foo b', 'foo')).toBe('a foo b'); @@ -12,3 +12,12 @@ test('removeAttachmentLinksFromMarkdown', () => { expect(removeAttachmentLinksFromMarkdown('a b', 'foo')).toBe('a b'); expect(removeAttachmentLinksFromMarkdown('a b', 'foo')).toBe('a b'); }); + +test('preparePasteAsMarkdownLink', () => { + expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 0}, 'bar')).toBeNull(); + expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 0}, 'https://gitea.com')).toBeNull(); + expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 3}, 'bar')).toBeNull(); + expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 3}, 'https://gitea.com')).toBe('[foo](https://gitea.com)'); + expect(pasteAsMarkdownLink({value: '(x)', selectionStart: 0, selectionEnd: 3}, 'https://gitea.com')).toBe('[(x)](https://gitea.com)'); + expect(pasteAsMarkdownLink({value: '[](url)', selectionStart: 3, selectionEnd: 6}, 'https://gitea.com')).toBeNull(); +}); diff --git a/web_src/js/features/comp/EditorUpload.ts b/web_src/js/features/comp/EditorUpload.ts index 9ccb2e0bc2d90..3f6d26658daac 100644 --- a/web_src/js/features/comp/EditorUpload.ts +++ b/web_src/js/features/comp/EditorUpload.ts @@ -118,20 +118,26 @@ export function removeAttachmentLinksFromMarkdown(text: string, fileUuid: string return text; } -function handleClipboardText(textarea: HTMLTextAreaElement, e: ClipboardEvent, text: string, isShiftDown: boolean) { - // pasting with "shift" means "paste as original content" in most applications - if (isShiftDown) return; // let the browser handle it - - // when pasting links over selected text, turn it into [text](link) +export function pasteAsMarkdownLink(textarea: {value: string, selectionStart: number, selectionEnd: number}, pastedText: string): string | null { const {value, selectionStart, selectionEnd} = textarea; const selectedText = value.substring(selectionStart, selectionEnd); - const trimmedText = text.trim(); + const trimmedText = pastedText.trim(); const beforeSelection = value.substring(0, selectionStart); const afterSelection = value.substring(selectionEnd); const isInMarkdownLink = beforeSelection.endsWith('](') && afterSelection.startsWith(')'); - if (selectedText && isUrl(trimmedText) && !isUrl(selectedText) && !isInMarkdownLink) { + const asMarkdownLink = selectedText && isUrl(trimmedText) && !isUrl(selectedText) && !isInMarkdownLink; + return asMarkdownLink ? `[${selectedText}](${trimmedText})` : null; +} + +function handleClipboardText(textarea: HTMLTextAreaElement, e: ClipboardEvent, pastedText: string, isShiftDown: boolean) { + // pasting with "shift" means "paste as original content" in most applications + if (isShiftDown) return; // let the browser handle it + + // when pasting links over selected text, turn it into [text](link) + const pastedAsMarkdown = pasteAsMarkdownLink(textarea, pastedText); + if (pastedText) { e.preventDefault(); - replaceTextareaSelection(textarea, `[${selectedText}](${trimmedText})`); + replaceTextareaSelection(textarea, pastedAsMarkdown); } // else, let the browser handle it } From 5b71c30a528bda4e81c139cdee47bd2313c4f212 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 17 Jun 2025 19:50:37 +0800 Subject: [PATCH 3/4] add one more test --- web_src/js/features/comp/EditorUpload.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/web_src/js/features/comp/EditorUpload.test.ts b/web_src/js/features/comp/EditorUpload.test.ts index 4f5377ee1c382..19a3dc7a23230 100644 --- a/web_src/js/features/comp/EditorUpload.test.ts +++ b/web_src/js/features/comp/EditorUpload.test.ts @@ -20,4 +20,5 @@ test('preparePasteAsMarkdownLink', () => { expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 3}, 'https://gitea.com')).toBe('[foo](https://gitea.com)'); expect(pasteAsMarkdownLink({value: '(x)', selectionStart: 0, selectionEnd: 3}, 'https://gitea.com')).toBe('[(x)](https://gitea.com)'); expect(pasteAsMarkdownLink({value: '[](url)', selectionStart: 3, selectionEnd: 6}, 'https://gitea.com')).toBeNull(); + expect(pasteAsMarkdownLink({value: 'https://example.com', selectionStart: 0, selectionEnd: 19}, 'https://gitea.com')).toBeNull(); }); From 812d4873ae283455ebfdcb32d77ab1c26f316b28 Mon Sep 17 00:00:00 2001 From: wxiaoguang Date: Tue, 17 Jun 2025 19:56:51 +0800 Subject: [PATCH 4/4] fine tune tests --- web_src/js/features/comp/EditorUpload.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web_src/js/features/comp/EditorUpload.test.ts b/web_src/js/features/comp/EditorUpload.test.ts index 19a3dc7a23230..e6e5f4de13a8e 100644 --- a/web_src/js/features/comp/EditorUpload.test.ts +++ b/web_src/js/features/comp/EditorUpload.test.ts @@ -18,7 +18,7 @@ test('preparePasteAsMarkdownLink', () => { expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 0}, 'https://gitea.com')).toBeNull(); expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 3}, 'bar')).toBeNull(); expect(pasteAsMarkdownLink({value: 'foo', selectionStart: 0, selectionEnd: 3}, 'https://gitea.com')).toBe('[foo](https://gitea.com)'); - expect(pasteAsMarkdownLink({value: '(x)', selectionStart: 0, selectionEnd: 3}, 'https://gitea.com')).toBe('[(x)](https://gitea.com)'); + expect(pasteAsMarkdownLink({value: '..(url)', selectionStart: 3, selectionEnd: 6}, 'https://gitea.com')).toBe('[url](https://gitea.com)'); expect(pasteAsMarkdownLink({value: '[](url)', selectionStart: 3, selectionEnd: 6}, 'https://gitea.com')).toBeNull(); expect(pasteAsMarkdownLink({value: 'https://example.com', selectionStart: 0, selectionEnd: 19}, 'https://gitea.com')).toBeNull(); });