diff --git a/README.md b/README.md index ae46b41..7829ef4 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,25 @@ Some ``s are not meant to be pasted as markdown; for example, a file cont
``` +### 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 ``` diff --git a/src/index.ts b/src/index.ts index 2f84aee..eb272ee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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) diff --git a/src/option-config.ts b/src/option-config.ts new file mode 100644 index 0000000..46d42d6 --- /dev/null +++ b/src/option-config.ts @@ -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*/ +} diff --git a/src/paste-keyboard-shortcut-helper.ts b/src/paste-keyboard-shortcut-helper.ts index f2a5261..e3c515a 100644 --- a/src/paste-keyboard-shortcut-helper.ts +++ b/src/paste-keyboard-shortcut-helper.ts @@ -1,3 +1,5 @@ +import {OptionConfig} from './option-config' + const skipFormattingMap = new WeakMap() function setSkipFormattingFlag(event: KeyboardEvent): void { @@ -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) diff --git a/src/paste-markdown-link.ts b/src/paste-markdown-link.ts index 75c7159..aa6470d 100644 --- a/src/paste-markdown-link.ts +++ b/src/paste-markdown-link.ts @@ -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() + +export function install(el: HTMLElement, optionConfig?: OptionConfig): void { + pasteLinkAsPlainTextOverSelectedTextMap.set(el, optionConfig?.defaultPlainTextPaste?.urlLinks === true) el.addEventListener('paste', onPaste) } @@ -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 @@ -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 diff --git a/test/test.js b/test/test.js index 12689f4..f2ee67a 100644 --- a/test/test.js +++ b/test/test.js @@ -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.' @@ -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) {