From c8cab7891bb35143cec0fac25561818a22cf78b1 Mon Sep 17 00:00:00 2001 From: Andrew L Date: Thu, 15 Sep 2022 19:51:47 +0200 Subject: [PATCH 1/6] Support paste config --- src/index.ts | 18 ++++++++++++++++-- src/option-config.ts | 7 +++++++ src/paste-markdown-link.ts | 10 +++++++++- test/test.js | 38 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 src/option-config.ts diff --git a/src/index.ts b/src/index.ts index 2f84aee..37dbd28 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +import {OptionConfig, PASTE_AS_PLAIN_TEXT_ATTRIBUTE} from './option-config' import {install as installHTML, uninstall as uninstallHTML} from './paste-markdown-html' import {install as installImageLink, uninstall as uninstallImageLink} from './paste-markdown-image-link' import {install as installLink, uninstall as uninstallLink} from './paste-markdown-link' @@ -12,9 +13,10 @@ interface Subscription { unsubscribe: () => void } -function subscribe(el: HTMLElement): Subscription { - installSkipFormatting(el, installTable, installImageLink, installLink, installText, installHTML) +function subscribe(el: HTMLElement, optionConfig?: OptionConfig): Subscription { + markElementWithConfigIfNeeded(el, optionConfig) + installSkipFormatting(el, installTable, installImageLink, installLink, installText, installHTML) return { unsubscribe: () => { uninstallSkipFormatting(el) @@ -27,6 +29,18 @@ function subscribe(el: HTMLElement): Subscription { } } +function markElementWithConfigIfNeeded(el: HTMLElement, optionConfig?: OptionConfig) { + // eslint-disable-next-line no-console + console.log(optionConfig) + + if (optionConfig?.pasteAsPlainText) { + // eslint-disable-next-line no-console + console.log(optionConfig?.pasteAsPlainText, 'HERE') + + el.setAttribute(PASTE_AS_PLAIN_TEXT_ATTRIBUTE, 'true') + } +} + export { subscribe, installHTML, diff --git a/src/option-config.ts b/src/option-config.ts new file mode 100644 index 0000000..bf0b179 --- /dev/null +++ b/src/option-config.ts @@ -0,0 +1,7 @@ +const PASTE_AS_PLAIN_TEXT_ATTRIBUTE = 'data-paste-as-plain-text' + +interface OptionConfig { + pasteAsPlainText?: boolean +} + +export {PASTE_AS_PLAIN_TEXT_ATTRIBUTE, OptionConfig} diff --git a/src/paste-markdown-link.ts b/src/paste-markdown-link.ts index 75c7159..d0d7d9a 100644 --- a/src/paste-markdown-link.ts +++ b/src/paste-markdown-link.ts @@ -1,3 +1,4 @@ +import {PASTE_AS_PLAIN_TEXT_ATTRIBUTE} from './option-config' import {insertText} from './text' import {shouldSkipFormatting} from './paste-keyboard-shortcut-helper' @@ -11,7 +12,13 @@ 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 shouldPastePlainText = element.hasAttribute(PASTE_AS_PLAIN_TEXT_ATTRIBUTE) + const shouldSkipDefaultBehavior = shouldSkipFormatting(element) + + if ((!shouldPastePlainText && shouldSkipDefaultBehavior) || (shouldPastePlainText && !shouldSkipDefaultBehavior)) { + return + } const transfer = event.clipboardData if (!transfer || !hasPlainText(transfer)) return @@ -26,6 +33,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..2255604 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 pasteAsPlainText 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 pasteAsPlainText 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 pasteAsPlainText 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, pasteAsPlainText) { + // Clear the before test subscription with no config and re-subscribe with config + subscription.unsubscribe() + return subscribe(textarea, {pasteAsPlainText}) +} + function paste(textarea, data) { const dataTransfer = new DataTransfer() for (const key in data) { From bd9a7fb90c8bc065f460b6df3c7959f9b1b97a0f Mon Sep 17 00:00:00 2001 From: Andrew L Date: Fri, 16 Sep 2022 20:45:05 +0200 Subject: [PATCH 2/6] Remove debug statements --- src/index.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/index.ts b/src/index.ts index 37dbd28..69f20c0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -30,13 +30,7 @@ function subscribe(el: HTMLElement, optionConfig?: OptionConfig): Subscription { } function markElementWithConfigIfNeeded(el: HTMLElement, optionConfig?: OptionConfig) { - // eslint-disable-next-line no-console - console.log(optionConfig) - if (optionConfig?.pasteAsPlainText) { - // eslint-disable-next-line no-console - console.log(optionConfig?.pasteAsPlainText, 'HERE') - el.setAttribute(PASTE_AS_PLAIN_TEXT_ATTRIBUTE, 'true') } } From cd58a5290f2fe5cb3265ac1e5b0d746dc51ef048 Mon Sep 17 00:00:00 2001 From: Andrew L Date: Fri, 16 Sep 2022 21:06:35 +0200 Subject: [PATCH 3/6] Update to use weak map --- src/index.ts | 12 ++---------- src/option-config.ts | 6 +----- src/paste-keyboard-shortcut-helper.ts | 13 ++++++++++++- src/paste-markdown-link.ts | 9 ++++++--- 4 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/index.ts b/src/index.ts index 69f20c0..768f668 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,3 @@ -import {OptionConfig, PASTE_AS_PLAIN_TEXT_ATTRIBUTE} from './option-config' import {install as installHTML, uninstall as uninstallHTML} from './paste-markdown-html' import {install as installImageLink, uninstall as uninstallImageLink} from './paste-markdown-image-link' import {install as installLink, uninstall as uninstallLink} from './paste-markdown-link' @@ -8,15 +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, optionConfig?: OptionConfig): Subscription { - markElementWithConfigIfNeeded(el, optionConfig) - - installSkipFormatting(el, installTable, installImageLink, installLink, installText, installHTML) + installSkipFormatting(el, [installTable, installImageLink, installText, installHTML], [installLink], optionConfig) return { unsubscribe: () => { uninstallSkipFormatting(el) @@ -29,12 +27,6 @@ function subscribe(el: HTMLElement, optionConfig?: OptionConfig): Subscription { } } -function markElementWithConfigIfNeeded(el: HTMLElement, optionConfig?: OptionConfig) { - if (optionConfig?.pasteAsPlainText) { - el.setAttribute(PASTE_AS_PLAIN_TEXT_ATTRIBUTE, 'true') - } -} - export { subscribe, installHTML, diff --git a/src/option-config.ts b/src/option-config.ts index bf0b179..d4d727a 100644 --- a/src/option-config.ts +++ b/src/option-config.ts @@ -1,7 +1,3 @@ -const PASTE_AS_PLAIN_TEXT_ATTRIBUTE = 'data-paste-as-plain-text' - -interface OptionConfig { +export interface OptionConfig { pasteAsPlainText?: boolean } - -export {PASTE_AS_PLAIN_TEXT_ATTRIBUTE, OptionConfig} diff --git a/src/paste-keyboard-shortcut-helper.ts b/src/paste-keyboard-shortcut-helper.ts index f2a5261..aab79a8 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,13 +23,22 @@ 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) => void>, + installCallbacksWithOptions: Array<(el: HTMLElement, optionConfig?: OptionConfig) => void>, + optionConfig?: OptionConfig +): void { el.addEventListener('keydown', setSkipFormattingFlag) for (const installCallback of installCallbacks) { installCallback(el) } + for (const installCallback of installCallbacksWithOptions) { + installCallback(el, optionConfig) + } + el.addEventListener('paste', unsetSkipFormattedFlag) } diff --git a/src/paste-markdown-link.ts b/src/paste-markdown-link.ts index d0d7d9a..1ded924 100644 --- a/src/paste-markdown-link.ts +++ b/src/paste-markdown-link.ts @@ -1,8 +1,11 @@ -import {PASTE_AS_PLAIN_TEXT_ATTRIBUTE} from './option-config' +import {OptionConfig} from './option-config' import {insertText} from './text' import {shouldSkipFormatting} from './paste-keyboard-shortcut-helper' -export function install(el: HTMLElement): void { +const pasteAsPlainTextMap = new WeakMap() + +export function install(el: HTMLElement, optionConfig?: OptionConfig): void { + pasteAsPlainTextMap.set(el, optionConfig?.pasteAsPlainText === true) el.addEventListener('paste', onPaste) } @@ -13,7 +16,7 @@ export function uninstall(el: HTMLElement): void { function onPaste(event: ClipboardEvent) { const {currentTarget: el} = event const element = el as HTMLElement - const shouldPastePlainText = element.hasAttribute(PASTE_AS_PLAIN_TEXT_ATTRIBUTE) + const shouldPastePlainText = pasteAsPlainTextMap.get(element) ?? false const shouldSkipDefaultBehavior = shouldSkipFormatting(element) if ((!shouldPastePlainText && shouldSkipDefaultBehavior) || (shouldPastePlainText && !shouldSkipDefaultBehavior)) { From 26613065ba4e242eaad5d3bffef0022a8ec06bf3 Mon Sep 17 00:00:00 2001 From: Andrew L Date: Tue, 20 Sep 2022 13:21:27 +0200 Subject: [PATCH 4/6] Make config flag more specific --- src/option-config.ts | 2 +- src/paste-markdown-link.ts | 11 +++++++---- test/test.js | 10 +++++----- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/option-config.ts b/src/option-config.ts index d4d727a..0e91bfd 100644 --- a/src/option-config.ts +++ b/src/option-config.ts @@ -1,3 +1,3 @@ export interface OptionConfig { - pasteAsPlainText?: boolean + pasteLinkAsPlainTextOverSelectedText?: boolean } diff --git a/src/paste-markdown-link.ts b/src/paste-markdown-link.ts index 1ded924..1e4e158 100644 --- a/src/paste-markdown-link.ts +++ b/src/paste-markdown-link.ts @@ -2,10 +2,10 @@ import {OptionConfig} from './option-config' import {insertText} from './text' import {shouldSkipFormatting} from './paste-keyboard-shortcut-helper' -const pasteAsPlainTextMap = new WeakMap() +const pasteLinkAsPlainTextOverSelectedTextMap = new WeakMap() export function install(el: HTMLElement, optionConfig?: OptionConfig): void { - pasteAsPlainTextMap.set(el, optionConfig?.pasteAsPlainText === true) + pasteLinkAsPlainTextOverSelectedTextMap.set(el, optionConfig?.pasteLinkAsPlainTextOverSelectedText === true) el.addEventListener('paste', onPaste) } @@ -16,10 +16,13 @@ export function uninstall(el: HTMLElement): void { function onPaste(event: ClipboardEvent) { const {currentTarget: el} = event const element = el as HTMLElement - const shouldPastePlainText = pasteAsPlainTextMap.get(element) ?? false + const shouldPasteAsPlainText = pasteLinkAsPlainTextOverSelectedTextMap.get(element) ?? false const shouldSkipDefaultBehavior = shouldSkipFormatting(element) - if ((!shouldPastePlainText && shouldSkipDefaultBehavior) || (shouldPastePlainText && !shouldSkipDefaultBehavior)) { + if ( + (!shouldPasteAsPlainText && shouldSkipDefaultBehavior) || + (shouldPasteAsPlainText && !shouldSkipDefaultBehavior) + ) { return } diff --git a/test/test.js b/test/test.js index 2255604..bcb6f42 100644 --- a/test/test.js +++ b/test/test.js @@ -43,7 +43,7 @@ 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 pasteAsPlainText is false', function () { + 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 @@ -53,7 +53,7 @@ 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 pasteAsPlainText is true and skip format flag is true', function () { + 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 @@ -64,7 +64,7 @@ describe('paste-markdown', function () { assert.equal(textarea.value, 'The examples can be found [here](https://github.com).') }) - it('pastes as plain text on selected text if pasteAsPlainText is true', function () { + 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 @@ -385,10 +385,10 @@ function dispatchSkipFormattingKeyEvent(textarea) { ) } -function subscribeWithOptionConfig(subscription, textarea, pasteAsPlainText) { +function subscribeWithOptionConfig(subscription, textarea, pasteLinkAsPlainTextOverSelectedText) { // Clear the before test subscription with no config and re-subscribe with config subscription.unsubscribe() - return subscribe(textarea, {pasteAsPlainText}) + return subscribe(textarea, {pasteLinkAsPlainTextOverSelectedText}) } function paste(textarea, data) { From 9bd8558052bb40cf3eb1e95b0362492f016b9400 Mon Sep 17 00:00:00 2001 From: Andrew L Date: Tue, 20 Sep 2022 19:57:10 +0200 Subject: [PATCH 5/6] PoC for granual support --- src/option-config.ts | 12 +++++++++++- src/paste-markdown-link.ts | 2 +- test/test.js | 4 ++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/option-config.ts b/src/option-config.ts index 0e91bfd..1b11cbc 100644 --- a/src/option-config.ts +++ b/src/option-config.ts @@ -1,3 +1,13 @@ export interface OptionConfig { - pasteLinkAsPlainTextOverSelectedText?: boolean + defaultPlainTextPaste?: PlainTextParams +} + +interface PlainTextParams { + urlLinks?: boolean + + // Not currently implemented behavior + /*imageLinks?: boolean + html?: boolean + table?: boolean + text?: boolean*/ } diff --git a/src/paste-markdown-link.ts b/src/paste-markdown-link.ts index 1e4e158..aa6470d 100644 --- a/src/paste-markdown-link.ts +++ b/src/paste-markdown-link.ts @@ -5,7 +5,7 @@ import {shouldSkipFormatting} from './paste-keyboard-shortcut-helper' const pasteLinkAsPlainTextOverSelectedTextMap = new WeakMap() export function install(el: HTMLElement, optionConfig?: OptionConfig): void { - pasteLinkAsPlainTextOverSelectedTextMap.set(el, optionConfig?.pasteLinkAsPlainTextOverSelectedText === true) + pasteLinkAsPlainTextOverSelectedTextMap.set(el, optionConfig?.defaultPlainTextPaste?.urlLinks === true) el.addEventListener('paste', onPaste) } diff --git a/test/test.js b/test/test.js index bcb6f42..f2ee67a 100644 --- a/test/test.js +++ b/test/test.js @@ -385,10 +385,10 @@ function dispatchSkipFormattingKeyEvent(textarea) { ) } -function subscribeWithOptionConfig(subscription, textarea, pasteLinkAsPlainTextOverSelectedText) { +function subscribeWithOptionConfig(subscription, textarea, urlLinks) { // Clear the before test subscription with no config and re-subscribe with config subscription.unsubscribe() - return subscribe(textarea, {pasteLinkAsPlainTextOverSelectedText}) + return subscribe(textarea, {defaultPlainTextPaste: {urlLinks}}) } function paste(textarea, data) { From c81e20ba52e664b8405966bf0faffed67497181d Mon Sep 17 00:00:00 2001 From: Andrew L Date: Wed, 21 Sep 2022 08:20:10 +0100 Subject: [PATCH 6/6] Update readme --- README.md | 19 +++++++++++++++++++ src/index.ts | 2 +- src/option-config.ts | 2 +- src/paste-keyboard-shortcut-helper.ts | 7 +------ 4 files changed, 22 insertions(+), 8 deletions(-) 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 768f668..eb272ee 100644 --- a/src/index.ts +++ b/src/index.ts @@ -14,7 +14,7 @@ interface Subscription { } function subscribe(el: HTMLElement, optionConfig?: OptionConfig): Subscription { - installSkipFormatting(el, [installTable, installImageLink, installText, installHTML], [installLink], optionConfig) + installSkipFormatting(el, [installTable, installImageLink, installLink, installText, installHTML], optionConfig) return { unsubscribe: () => { uninstallSkipFormatting(el) diff --git a/src/option-config.ts b/src/option-config.ts index 1b11cbc..46d42d6 100644 --- a/src/option-config.ts +++ b/src/option-config.ts @@ -8,6 +8,6 @@ interface PlainTextParams { // Not currently implemented behavior /*imageLinks?: boolean html?: boolean - table?: boolean + tables?: boolean text?: boolean*/ } diff --git a/src/paste-keyboard-shortcut-helper.ts b/src/paste-keyboard-shortcut-helper.ts index aab79a8..e3c515a 100644 --- a/src/paste-keyboard-shortcut-helper.ts +++ b/src/paste-keyboard-shortcut-helper.ts @@ -25,17 +25,12 @@ export function shouldSkipFormatting(el: HTMLElement): boolean { export function installAround( el: HTMLElement, - installCallbacks: Array<(el: HTMLElement) => void>, - installCallbacksWithOptions: Array<(el: HTMLElement, optionConfig?: OptionConfig) => void>, + installCallbacks: Array<(el: HTMLElement, optionConfig?: OptionConfig) => void>, optionConfig?: OptionConfig ): void { el.addEventListener('keydown', setSkipFormattingFlag) for (const installCallback of installCallbacks) { - installCallback(el) - } - - for (const installCallback of installCallbacksWithOptions) { installCallback(el, optionConfig) }