Skip to content

Commit

Permalink
Merge pull request #68 from anleac/paste-config
Browse files Browse the repository at this point in the history
Add architecture for granual pasteAsPlainText support
  • Loading branch information
manuelpuyol authored Sep 21, 2022
2 parents 6a2f6df + c81e20b commit 029c5e4
Show file tree
Hide file tree
Showing 6 changed files with 97 additions and 7 deletions.
19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,25 @@ Some `<table>`s are not meant to be pasted as markdown; for example, a file cont
</table>
```

### Granular control for pasting as plain text

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.

Our config support looks as follows:

```js
import {subscribe} from '@github/paste-markdown'

// Subscribe the behavior to the textarea with pasting URL links as plain text by default.
subscribe(document.querySelector('textarea[data-paste-markdown]'), {defaultPlainTextPaste: {urlLinks: true}})
```

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.

Only the `urlLinks` param is currently supported.

If there is no config passed in, or attributes missing, this will always default to `false`, being the existing behavior.

## Development

```
Expand Down
6 changes: 3 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ import {
} from './paste-keyboard-shortcut-helper'
import {install as installTable, uninstall as uninstallTable} from './paste-markdown-table'
import {install as installText, uninstall as uninstallText} from './paste-markdown-text'
import {OptionConfig} from './option-config'

interface Subscription {
unsubscribe: () => void
}

function subscribe(el: HTMLElement): Subscription {
installSkipFormatting(el, installTable, installImageLink, installLink, installText, installHTML)

function subscribe(el: HTMLElement, optionConfig?: OptionConfig): Subscription {
installSkipFormatting(el, [installTable, installImageLink, installLink, installText, installHTML], optionConfig)
return {
unsubscribe: () => {
uninstallSkipFormatting(el)
Expand Down
13 changes: 13 additions & 0 deletions src/option-config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export interface OptionConfig {
defaultPlainTextPaste?: PlainTextParams
}

interface PlainTextParams {
urlLinks?: boolean

// Not currently implemented behavior
/*imageLinks?: boolean
html?: boolean
tables?: boolean
text?: boolean*/
}
10 changes: 8 additions & 2 deletions src/paste-keyboard-shortcut-helper.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import {OptionConfig} from './option-config'

const skipFormattingMap = new WeakMap<HTMLElement, boolean>()

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

export function installAround(el: HTMLElement, ...installCallbacks: Array<(el: HTMLElement) => void>): void {
export function installAround(
el: HTMLElement,
installCallbacks: Array<(el: HTMLElement, optionConfig?: OptionConfig) => void>,
optionConfig?: OptionConfig
): void {
el.addEventListener('keydown', setSkipFormattingFlag)

for (const installCallback of installCallbacks) {
installCallback(el)
installCallback(el, optionConfig)
}

el.addEventListener('paste', unsetSkipFormattedFlag)
Expand Down
18 changes: 16 additions & 2 deletions src/paste-markdown-link.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import {OptionConfig} from './option-config'
import {insertText} from './text'
import {shouldSkipFormatting} from './paste-keyboard-shortcut-helper'

export function install(el: HTMLElement): void {
const pasteLinkAsPlainTextOverSelectedTextMap = new WeakMap<HTMLElement, boolean>()

export function install(el: HTMLElement, optionConfig?: OptionConfig): void {
pasteLinkAsPlainTextOverSelectedTextMap.set(el, optionConfig?.defaultPlainTextPaste?.urlLinks === true)
el.addEventListener('paste', onPaste)
}

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

function onPaste(event: ClipboardEvent) {
const {currentTarget: el} = event
if (shouldSkipFormatting(el as HTMLElement)) return
const element = el as HTMLElement
const shouldPasteAsPlainText = pasteLinkAsPlainTextOverSelectedTextMap.get(element) ?? false
const shouldSkipDefaultBehavior = shouldSkipFormatting(element)

if (
(!shouldPasteAsPlainText && shouldSkipDefaultBehavior) ||
(shouldPasteAsPlainText && !shouldSkipDefaultBehavior)
) {
return
}

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

const selectedText = field.value.substring(field.selectionStart, field.selectionEnd)
if (!selectedText.length) return

// Prevent linkification when replacing an URL
// Trim whitespace in case whitespace is selected by mistake or by intention
if (isURL(selectedText.trim())) return
Expand Down
38 changes: 38 additions & 0 deletions test/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,38 @@ describe('paste-markdown', function () {
assert.equal(textarea.value, 'The examples can be found [here](https://github.com).')
})

it('turns pasted urls on selected text into markdown links if pasteLinkAsPlainTextOverSelectedText is false', function () {
subscription = subscribeWithOptionConfig(subscription, textarea, false)

// eslint-disable-next-line i18n-text/no-en
textarea.value = 'The examples can be found here.'
textarea.setSelectionRange(26, 30)
paste(textarea, {'text/plain': 'https://github.com'})
assert.equal(textarea.value, 'The examples can be found [here](https://github.com).')
})

it('turns pasted urls on selected text into markdown links if pasteLinkAsPlainTextOverSelectedText is true and skip format flag is true', function () {
subscription = subscribeWithOptionConfig(subscription, textarea, true)

// eslint-disable-next-line i18n-text/no-en
textarea.value = 'The examples can be found here.'
textarea.setSelectionRange(26, 30)
dispatchSkipFormattingKeyEvent(textarea)
paste(textarea, {'text/plain': 'https://github.com'})
assert.equal(textarea.value, 'The examples can be found [here](https://github.com).')
})

it('pastes as plain text on selected text if pasteLinkAsPlainTextOverSelectedText is true', function () {
subscription = subscribeWithOptionConfig(subscription, textarea, true)

// eslint-disable-next-line i18n-text/no-en
textarea.value = 'The examples can be found here.'
textarea.setSelectionRange(26, 30)
paste(textarea, {'text/plain': 'https://github.com'})
// The text area will be unchanged at this stage as the paste won't be handled by our listener
assert.equal(textarea.value, 'The examples can be found here.')
})

it('creates a markdown link when the pasted url includes a trailing slash', function () {
// eslint-disable-next-line i18n-text/no-en
textarea.value = 'The examples can be found here.'
Expand Down Expand Up @@ -353,6 +385,12 @@ function dispatchSkipFormattingKeyEvent(textarea) {
)
}

function subscribeWithOptionConfig(subscription, textarea, urlLinks) {
// Clear the before test subscription with no config and re-subscribe with config
subscription.unsubscribe()
return subscribe(textarea, {defaultPlainTextPaste: {urlLinks}})
}

function paste(textarea, data) {
const dataTransfer = new DataTransfer()
for (const key in data) {
Expand Down

0 comments on commit 029c5e4

Please sign in to comment.