diff --git a/src/components/markdown-editor.test.tsx b/src/components/markdown-editor.test.tsx
index 3d8d1cb..6a1c8fc 100644
--- a/src/components/markdown-editor.test.tsx
+++ b/src/components/markdown-editor.test.tsx
@@ -132,6 +132,89 @@ describe('MarkdownEditor', () => {
expect(screen.getByRole('textbox', { name: 'Content' })).toHaveAttribute('aria-invalid', 'true')
})
+ describe('smart link paste', () => {
+ function pasteUrl(textarea: HTMLTextAreaElement, url: string) {
+ const event = new Event('paste', { bubbles: true, cancelable: true })
+ Object.defineProperty(event, 'clipboardData', {
+ value: { getData: (type: string) => (type === 'text/plain' ? url : '') },
+ })
+ textarea.dispatchEvent(event)
+ return event
+ }
+
+ it('wraps selected text as markdown link when pasting a URL', () => {
+ const onChange = vi.fn()
+ render(
+
+ )
+ const textarea = screen.getByRole('textbox', { name: 'Content' }) as HTMLTextAreaElement
+
+ // Select "this"
+ textarea.setSelectionRange(6, 10)
+ pasteUrl(textarea, 'https://example.com')
+
+ expect(onChange).toHaveBeenCalledWith('check [this](https://example.com) out')
+ })
+
+ it('does not intercept paste when no text is selected', () => {
+ const onChange = vi.fn()
+ render()
+ const textarea = screen.getByRole('textbox', { name: 'Content' }) as HTMLTextAreaElement
+
+ textarea.setSelectionRange(6, 6)
+ const event = pasteUrl(textarea, 'https://example.com')
+
+ // Event should not be prevented — default paste behavior
+ expect(event.defaultPrevented).toBe(false)
+ expect(onChange).not.toHaveBeenCalled()
+ })
+
+ it('does not intercept paste when pasted text is not a URL', () => {
+ const onChange = vi.fn()
+ render(
+
+ )
+ const textarea = screen.getByRole('textbox', { name: 'Content' }) as HTMLTextAreaElement
+
+ textarea.setSelectionRange(6, 11)
+ const event = pasteUrl(textarea, 'not a url')
+
+ expect(event.defaultPrevented).toBe(false)
+ expect(onChange).not.toHaveBeenCalled()
+ })
+
+ it('handles http:// URLs', () => {
+ const onChange = vi.fn()
+ render(
+
+ )
+ const textarea = screen.getByRole('textbox', { name: 'Content' }) as HTMLTextAreaElement
+
+ textarea.setSelectionRange(6, 10)
+ pasteUrl(textarea, 'http://example.com')
+
+ expect(onChange).toHaveBeenCalledWith('click [here](http://example.com) please')
+ })
+
+ it('places cursor after the inserted link', () => {
+ const onChange = vi.fn()
+ render(
+
+ )
+ const textarea = screen.getByRole('textbox', { name: 'Content' }) as HTMLTextAreaElement
+
+ textarea.setSelectionRange(4, 8)
+ pasteUrl(textarea, 'https://docs.example.com')
+
+ expect(onChange).toHaveBeenCalledWith('see [docs](https://docs.example.com) now')
+ })
+ })
+
it('passes axe accessibility check', async () => {
const { container } = render(
diff --git a/src/components/markdown-editor.tsx b/src/components/markdown-editor.tsx
index 8bfd0e4..359672f 100644
--- a/src/components/markdown-editor.tsx
+++ b/src/components/markdown-editor.tsx
@@ -59,6 +59,34 @@ export function MarkdownEditor({
[value, onChange]
)
+ const handlePaste = useCallback(
+ (e: React.ClipboardEvent) => {
+ const textarea = textareaRef.current
+ if (!textarea) return
+
+ const start = textarea.selectionStart
+ const end = textarea.selectionEnd
+ if (start === end) return // No selection — let default paste happen
+
+ const pasted = e.clipboardData.getData('text/plain')
+ if (!/^https?:\/\/\S+$/.test(pasted)) return // Not a URL
+
+ e.preventDefault()
+ const before = value.slice(0, start)
+ const selected = value.slice(start, end)
+ const after = value.slice(end)
+ const link = `[${selected}](${pasted})`
+ onChange(before + link + after)
+
+ const cursorPos = start + link.length
+ requestAnimationFrame(() => {
+ textarea.focus()
+ textarea.setSelectionRange(cursorPos, cursorPos)
+ })
+ },
+ [value, onChange]
+ )
+
const handleToolbarKeyDown = useCallback(
(e: React.KeyboardEvent) => {
const buttons = toolbarRef.current?.querySelectorAll('button')
@@ -127,6 +155,7 @@ export function MarkdownEditor({
id={id}
value={value}
onChange={(e) => onChange(e.target.value)}
+ onPaste={handlePaste}
placeholder={placeholder ?? 'Write your content using Markdown...'}
required={required}
aria-invalid={error ? 'true' : undefined}