Skip to content

Commit d206bbf

Browse files
authored
Merge pull request #23 from steffen/paste-urls-as-markdown-links
Add pasting URLs on selected text as Markdown links
2 parents cd891ff + 6c4781f commit d206bbf

File tree

6 files changed

+93
-10
lines changed

6 files changed

+93
-10
lines changed

README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Paste Markdown objects
22

33
- Paste spreadsheet cells and HTML tables as a Markdown tables.
4+
- Paste URLs on selected text as Markdown links.
45
- Paste image URLs as Markdown image links.
56
- Paste markdown as markdown. See [`@github/quote-selection`/Preserving markdown syntax](https://github.com/github/quote-selection/tree/9ae5f88f5bc3021f51d2dc9981eca83ce7cfe04f#preserving-markdown-syntax) for details.
67

@@ -13,7 +14,7 @@ $ npm install @github/paste-markdown
1314
## Usage
1415

1516
```js
16-
import subscribe from '@github/paste-markdown'
17+
import {subscribe} from '@github/paste-markdown'
1718

1819
// Subscribe the behavior to the textarea.
1920
subscribe(document.querySelector('textarea[data-paste-markdown]'))
@@ -26,7 +27,7 @@ be applied to any element matching a selector.
2627

2728
```js
2829
import {observe} from 'selector-observer'
29-
import subscribe from '@github/paste-markdown'
30+
import {subscribe} from '@github/paste-markdown'
3031

3132
// Subscribe the behavior to all matching textareas.
3233
observe('textarea[data-paste-markdown]', {subscribe})

examples/index.html

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,15 @@
4646

4747
<img src="https://github.com/hubot.png" width="100" alt="hubot">
4848

49-
<textarea cols="50" rows="10"></textarea>
49+
<p>Test by copying this page's URL and then selecting <i>here</i> in the textarea and pasting the URL.</p>
50+
51+
<textarea cols="50" rows="10">The examples can be found here.</textarea>
5052

5153
<script type="module">
52-
// import subscribe from '../dist/index.esm.js'
53-
import subscribe from 'https://unpkg.com/@github/paste-markdown/dist/index.esm.js'
54+
// import {subscribe} from '../dist/index.esm.js'
55+
import {subscribe} from 'https://unpkg.com/@github/paste-markdown/dist/index.esm.js'
5456
subscribe(document.querySelector('textarea'))
57+
installLink(document.querySelector('textarea'))
5558
</script>
5659
</body>
5760
</html>

src/index.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,36 @@
1-
import {install as installLink, uninstall as uninstallLink} from './paste-markdown-image-link'
1+
import {install as installImageLink, uninstall as uninstallImageLink} from './paste-markdown-image-link'
2+
import {install as installLink, uninstall as uninstallLink} from './paste-markdown-link'
23
import {install as installTable, uninstall as uninstallTable} from './paste-markdown-table'
34
import {install as installText, uninstall as uninstallText} from './paste-markdown-text'
45

56
interface Subscription {
67
unsubscribe: () => void
78
}
89

9-
export default function subscribe(el: HTMLElement): Subscription {
10+
function subscribe(el: HTMLElement): Subscription {
1011
installTable(el)
12+
installImageLink(el)
1113
installLink(el)
1214
installText(el)
1315

1416
return {
1517
unsubscribe: () => {
1618
uninstallTable(el)
19+
uninstallImageLink(el)
1720
uninstallLink(el)
1821
uninstallText(el)
1922
}
2023
}
2124
}
25+
26+
export {
27+
subscribe,
28+
installImageLink,
29+
installLink,
30+
installTable,
31+
installText,
32+
uninstallImageLink,
33+
uninstallTable,
34+
uninstallLink,
35+
uninstallText
36+
}

src/paste-markdown-link.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import {insertText} from './text'
2+
3+
export function install(el: HTMLElement): void {
4+
el.addEventListener('paste', onPaste)
5+
}
6+
7+
export function uninstall(el: HTMLElement): void {
8+
el.removeEventListener('paste', onPaste)
9+
}
10+
11+
function onPaste(event: ClipboardEvent) {
12+
const transfer = event.clipboardData
13+
if (!transfer || !hasPlainText(transfer)) return
14+
15+
const field = event.currentTarget
16+
if (!(field instanceof HTMLTextAreaElement)) return
17+
18+
const text = transfer.getData('text/plain')
19+
if (!text) return
20+
21+
if (isWithinLink(field)) return
22+
23+
event.stopPropagation()
24+
event.preventDefault()
25+
26+
const selectedText = field.value.substring(field.selectionStart, field.selectionEnd)
27+
28+
insertText(field, linkify(selectedText, text), {addNewline: false})
29+
}
30+
31+
function hasPlainText(transfer: DataTransfer): boolean {
32+
return Array.from(transfer.types).includes('text/plain')
33+
}
34+
35+
function isWithinLink(textarea: HTMLTextAreaElement): boolean {
36+
const selectionStart = textarea.selectionStart || 0
37+
38+
if (selectionStart > 1) {
39+
const previousChars = textarea.value.substring(selectionStart - 2, selectionStart)
40+
return previousChars === ']('
41+
} else {
42+
return false
43+
}
44+
}
45+
46+
function linkify(selectedText: string, text: string): string {
47+
return selectedText.length && isURL(text) ? `[${selectedText}](${text})` : text
48+
}
49+
50+
function isURL(url: string): boolean {
51+
return /^https?:\/\//i.test(url)
52+
}

src/text.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
1-
export function insertText(textarea: HTMLInputElement | HTMLTextAreaElement, text: string): void {
1+
export function insertText(
2+
textarea: HTMLInputElement | HTMLTextAreaElement,
3+
text: string,
4+
options = {addNewline: true}
5+
): void {
26
const beginning = textarea.value.substring(0, textarea.selectionStart || 0)
37
const remaining = textarea.value.substring(textarea.selectionEnd || 0)
48

5-
const newline = beginning.length === 0 || beginning.match(/\n$/) ? '' : '\n'
9+
const newline = !options.addNewline || beginning.length === 0 || beginning.match(/\n$/) ? '' : '\n'
610
const textBeforeCursor = beginning + newline + text
711

812
textarea.value = textBeforeCursor + remaining

test/test.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import subscribe from '../dist/index.esm.js'
1+
import {subscribe} from '../dist/index.esm.js'
22

33
describe('paste-markdown', function () {
44
describe('installed on textarea', function () {
@@ -23,6 +23,14 @@ describe('paste-markdown', function () {
2323
assert.include(textarea.value, '![](https://github.com/github.png)\n\n![](https://github.com/hubot.png)')
2424
})
2525

26+
it('turns pasted urls on selected text into markdown links', function () {
27+
// eslint-disable-next-line i18n-text/no-en
28+
textarea.value = 'The examples can be found here.'
29+
textarea.setSelectionRange(26, 30)
30+
paste(textarea, {'text/plain': 'https://github.com'})
31+
assert.equal(textarea.value, 'The examples can be found [here](https://github.com).')
32+
})
33+
2634
it('turns html tables into markdown', function () {
2735
const data = {
2836
'text/html': `

0 commit comments

Comments
 (0)