diff --git a/app/components/Edit/NoteSelect.vue b/app/components/Edit/NoteSelect.vue index 2a4a652..3c71f15 100644 --- a/app/components/Edit/NoteSelect.vue +++ b/app/components/Edit/NoteSelect.vue @@ -7,7 +7,7 @@ const isEditing = ref(note.value === ""); const render = useTemplateRef("render"); - const { handleKeyDown } = useMarkdownEditor(note); + const { handleKeyDown, handlePaste } = useMarkdownEditor(note); marked.use({ renderer: { @@ -87,6 +87,7 @@ v-if="isEditing" v-model="note" @keydown="handleKeyDown" + @paste="handlePaste" data-testid="note-input" class="border-secondary h-full w-full resize-none rounded border pl-1 focus:outline-none focus:border-secondary-hover" :placeholder="t('note')" diff --git a/app/composables/useMarkdownEditor.ts b/app/composables/useMarkdownEditor.ts index 230e122..9908804 100644 --- a/app/composables/useMarkdownEditor.ts +++ b/app/composables/useMarkdownEditor.ts @@ -50,6 +50,15 @@ function continueList(state: State): string { return ""; } +function isValidUrl(string: string): boolean { + try { + const url = new URL(string.trim()); + return url.protocol === "http:" || url.protocol === "https:"; + } catch { + return false; + } +} + export function useMarkdownEditor(note: ModelRef) { function handleKeyDown(e: KeyboardEvent) { const textarea = e.target as HTMLTextAreaElement; @@ -93,7 +102,36 @@ export function useMarkdownEditor(note: ModelRef) { } } + function handlePaste(event: ClipboardEvent) { + const textarea = event.target as HTMLTextAreaElement; + const start = textarea.selectionStart; + const end = textarea.selectionEnd; + + // Only convert if user has text selected + if (start === end) return; + + const pastedData = event.clipboardData?.getData("text")?.trim(); + if (!pastedData || !isValidUrl(pastedData)) return; + + event.preventDefault(); + + const selectedText = textarea.value.slice(start, end); + const markdownLink = `[${selectedText}](${pastedData})`; + + const newText = + textarea.value.slice(0, start) + markdownLink + textarea.value.slice(end); + + note.value = newText; + + const newCursorPos = start + markdownLink.length; + requestAnimationFrame(() => { + textarea.selectionStart = newCursorPos; + textarea.selectionEnd = newCursorPos; + }); + } + return { handleKeyDown, + handlePaste, }; }