Skip to content

Commit 029c5e4

Browse files
authored
Merge pull request #68 from anleac/paste-config
Add architecture for granual pasteAsPlainText support
2 parents 6a2f6df + c81e20b commit 029c5e4

6 files changed

+97
-7
lines changed

README.md

+19
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,25 @@ Some `<table>`s are not meant to be pasted as markdown; for example, a file cont
4444
</table>
4545
```
4646

47+
### Granular control for pasting as plain text
48+
49+
If you're wanting more granular support of pasting certain items as plain text by default, you can pass in the controls config at the `subscribe` level.
50+
51+
Our config support looks as follows:
52+
53+
```js
54+
import {subscribe} from '@github/paste-markdown'
55+
56+
// Subscribe the behavior to the textarea with pasting URL links as plain text by default.
57+
subscribe(document.querySelector('textarea[data-paste-markdown]'), {defaultPlainTextPaste: {urlLinks: true}})
58+
```
59+
60+
In this scenario above, pasting a URL over selected text will paste as plain text by default, but pasting a table will still paste as markdown by default.
61+
62+
Only the `urlLinks` param is currently supported.
63+
64+
If there is no config passed in, or attributes missing, this will always default to `false`, being the existing behavior.
65+
4766
## Development
4867

4968
```

src/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,14 @@ import {
77
} from './paste-keyboard-shortcut-helper'
88
import {install as installTable, uninstall as uninstallTable} from './paste-markdown-table'
99
import {install as installText, uninstall as uninstallText} from './paste-markdown-text'
10+
import {OptionConfig} from './option-config'
1011

1112
interface Subscription {
1213
unsubscribe: () => void
1314
}
1415

15-
function subscribe(el: HTMLElement): Subscription {
16-
installSkipFormatting(el, installTable, installImageLink, installLink, installText, installHTML)
17-
16+
function subscribe(el: HTMLElement, optionConfig?: OptionConfig): Subscription {
17+
installSkipFormatting(el, [installTable, installImageLink, installLink, installText, installHTML], optionConfig)
1818
return {
1919
unsubscribe: () => {
2020
uninstallSkipFormatting(el)

src/option-config.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export interface OptionConfig {
2+
defaultPlainTextPaste?: PlainTextParams
3+
}
4+
5+
interface PlainTextParams {
6+
urlLinks?: boolean
7+
8+
// Not currently implemented behavior
9+
/*imageLinks?: boolean
10+
html?: boolean
11+
tables?: boolean
12+
text?: boolean*/
13+
}

src/paste-keyboard-shortcut-helper.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import {OptionConfig} from './option-config'
2+
13
const skipFormattingMap = new WeakMap<HTMLElement, boolean>()
24

35
function setSkipFormattingFlag(event: KeyboardEvent): void {
@@ -21,11 +23,15 @@ export function shouldSkipFormatting(el: HTMLElement): boolean {
2123
return shouldSkipFormattingState
2224
}
2325

24-
export function installAround(el: HTMLElement, ...installCallbacks: Array<(el: HTMLElement) => void>): void {
26+
export function installAround(
27+
el: HTMLElement,
28+
installCallbacks: Array<(el: HTMLElement, optionConfig?: OptionConfig) => void>,
29+
optionConfig?: OptionConfig
30+
): void {
2531
el.addEventListener('keydown', setSkipFormattingFlag)
2632

2733
for (const installCallback of installCallbacks) {
28-
installCallback(el)
34+
installCallback(el, optionConfig)
2935
}
3036

3137
el.addEventListener('paste', unsetSkipFormattedFlag)

src/paste-markdown-link.ts

+16-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
import {OptionConfig} from './option-config'
12
import {insertText} from './text'
23
import {shouldSkipFormatting} from './paste-keyboard-shortcut-helper'
34

4-
export function install(el: HTMLElement): void {
5+
const pasteLinkAsPlainTextOverSelectedTextMap = new WeakMap<HTMLElement, boolean>()
6+
7+
export function install(el: HTMLElement, optionConfig?: OptionConfig): void {
8+
pasteLinkAsPlainTextOverSelectedTextMap.set(el, optionConfig?.defaultPlainTextPaste?.urlLinks === true)
59
el.addEventListener('paste', onPaste)
610
}
711

@@ -11,7 +15,16 @@ export function uninstall(el: HTMLElement): void {
1115

1216
function onPaste(event: ClipboardEvent) {
1317
const {currentTarget: el} = event
14-
if (shouldSkipFormatting(el as HTMLElement)) return
18+
const element = el as HTMLElement
19+
const shouldPasteAsPlainText = pasteLinkAsPlainTextOverSelectedTextMap.get(element) ?? false
20+
const shouldSkipDefaultBehavior = shouldSkipFormatting(element)
21+
22+
if (
23+
(!shouldPasteAsPlainText && shouldSkipDefaultBehavior) ||
24+
(shouldPasteAsPlainText && !shouldSkipDefaultBehavior)
25+
) {
26+
return
27+
}
1528

1629
const transfer = event.clipboardData
1730
if (!transfer || !hasPlainText(transfer)) return
@@ -26,6 +39,7 @@ function onPaste(event: ClipboardEvent) {
2639

2740
const selectedText = field.value.substring(field.selectionStart, field.selectionEnd)
2841
if (!selectedText.length) return
42+
2943
// Prevent linkification when replacing an URL
3044
// Trim whitespace in case whitespace is selected by mistake or by intention
3145
if (isURL(selectedText.trim())) return

test/test.js

+38
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,38 @@ describe('paste-markdown', function () {
4343
assert.equal(textarea.value, 'The examples can be found [here](https://github.com).')
4444
})
4545

46+
it('turns pasted urls on selected text into markdown links if pasteLinkAsPlainTextOverSelectedText is false', function () {
47+
subscription = subscribeWithOptionConfig(subscription, textarea, false)
48+
49+
// eslint-disable-next-line i18n-text/no-en
50+
textarea.value = 'The examples can be found here.'
51+
textarea.setSelectionRange(26, 30)
52+
paste(textarea, {'text/plain': 'https://github.com'})
53+
assert.equal(textarea.value, 'The examples can be found [here](https://github.com).')
54+
})
55+
56+
it('turns pasted urls on selected text into markdown links if pasteLinkAsPlainTextOverSelectedText is true and skip format flag is true', function () {
57+
subscription = subscribeWithOptionConfig(subscription, textarea, true)
58+
59+
// eslint-disable-next-line i18n-text/no-en
60+
textarea.value = 'The examples can be found here.'
61+
textarea.setSelectionRange(26, 30)
62+
dispatchSkipFormattingKeyEvent(textarea)
63+
paste(textarea, {'text/plain': 'https://github.com'})
64+
assert.equal(textarea.value, 'The examples can be found [here](https://github.com).')
65+
})
66+
67+
it('pastes as plain text on selected text if pasteLinkAsPlainTextOverSelectedText is true', function () {
68+
subscription = subscribeWithOptionConfig(subscription, textarea, true)
69+
70+
// eslint-disable-next-line i18n-text/no-en
71+
textarea.value = 'The examples can be found here.'
72+
textarea.setSelectionRange(26, 30)
73+
paste(textarea, {'text/plain': 'https://github.com'})
74+
// The text area will be unchanged at this stage as the paste won't be handled by our listener
75+
assert.equal(textarea.value, 'The examples can be found here.')
76+
})
77+
4678
it('creates a markdown link when the pasted url includes a trailing slash', function () {
4779
// eslint-disable-next-line i18n-text/no-en
4880
textarea.value = 'The examples can be found here.'
@@ -353,6 +385,12 @@ function dispatchSkipFormattingKeyEvent(textarea) {
353385
)
354386
}
355387

388+
function subscribeWithOptionConfig(subscription, textarea, urlLinks) {
389+
// Clear the before test subscription with no config and re-subscribe with config
390+
subscription.unsubscribe()
391+
return subscribe(textarea, {defaultPlainTextPaste: {urlLinks}})
392+
}
393+
356394
function paste(textarea, data) {
357395
const dataTransfer = new DataTransfer()
358396
for (const key in data) {

0 commit comments

Comments
 (0)