diff --git a/web_src/js/features/comp/EditorUpload.test.ts b/web_src/js/features/comp/EditorUpload.test.ts
index 55f3f74389..e6e5f4de13 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,13 @@ test('removeAttachmentLinksFromMarkdown', () => {
   expect(removeAttachmentLinksFromMarkdown('a  b', 'foo')).toBe('a  b');
   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: '..(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();
+});
diff --git a/web_src/js/features/comp/EditorUpload.ts b/web_src/js/features/comp/EditorUpload.ts
index f6d5731422..3f6d26658d 100644
--- a/web_src/js/features/comp/EditorUpload.ts
+++ b/web_src/js/features/comp/EditorUpload.ts
@@ -118,17 +118,26 @@ export function removeAttachmentLinksFromMarkdown(text: string, fileUuid: string
   return text;
 }
 
-function handleClipboardText(textarea: HTMLTextAreaElement, e: ClipboardEvent, text: string, isShiftDown: boolean) {
+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 = pastedText.trim();
+  const beforeSelection = value.substring(0, selectionStart);
+  const afterSelection = value.substring(selectionEnd);
+  const isInMarkdownLink = beforeSelection.endsWith('](') && afterSelection.startsWith(')');
+  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 {value, selectionStart, selectionEnd} = textarea;
-  const selectedText = value.substring(selectionStart, selectionEnd);
-  const trimmedText = text.trim();
-  if (selectedText && isUrl(trimmedText) && !isUrl(selectedText)) {
+  const pastedAsMarkdown = pasteAsMarkdownLink(textarea, pastedText);
+  if (pastedText) {
     e.preventDefault();
-    replaceTextareaSelection(textarea, `[${selectedText}](${trimmedText})`);
+    replaceTextareaSelection(textarea, pastedAsMarkdown);
   }
   // else, let the browser handle it
 }
 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: '..(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();
+});
diff --git a/web_src/js/features/comp/EditorUpload.ts b/web_src/js/features/comp/EditorUpload.ts
index f6d5731422..3f6d26658d 100644
--- a/web_src/js/features/comp/EditorUpload.ts
+++ b/web_src/js/features/comp/EditorUpload.ts
@@ -118,17 +118,26 @@ export function removeAttachmentLinksFromMarkdown(text: string, fileUuid: string
   return text;
 }
 
-function handleClipboardText(textarea: HTMLTextAreaElement, e: ClipboardEvent, text: string, isShiftDown: boolean) {
+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 = pastedText.trim();
+  const beforeSelection = value.substring(0, selectionStart);
+  const afterSelection = value.substring(selectionEnd);
+  const isInMarkdownLink = beforeSelection.endsWith('](') && afterSelection.startsWith(')');
+  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 {value, selectionStart, selectionEnd} = textarea;
-  const selectedText = value.substring(selectionStart, selectionEnd);
-  const trimmedText = text.trim();
-  if (selectedText && isUrl(trimmedText) && !isUrl(selectedText)) {
+  const pastedAsMarkdown = pasteAsMarkdownLink(textarea, pastedText);
+  if (pastedText) {
     e.preventDefault();
-    replaceTextareaSelection(textarea, `[${selectedText}](${trimmedText})`);
+    replaceTextareaSelection(textarea, pastedAsMarkdown);
   }
   // else, let the browser handle it
 }