Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add architecture for granual pasteAsPlainText support #68

Merged
merged 6 commits into from
Sep 21, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you this is great 👏

## 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

great!


// 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