Skip to content

Commit

Permalink
Add a prop to control URL pasting behavior in MarkdownEditor (#2368)
Browse files Browse the repository at this point in the history
* Upgrade `@github/paste-markdown`

* Add `pasteUrlsAsPlainText` prop to `MarkdownEditor` component

* Create weak-peaches-teach.md

* Change from `patch` to `minor`
  • Loading branch information
iansan5653 authored Sep 23, 2022
1 parent df2a834 commit aeb6152
Show file tree
Hide file tree
Showing 7 changed files with 81 additions and 15 deletions.
5 changes: 5 additions & 0 deletions .changeset/weak-peaches-teach.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@primer/react": minor
---

Add `pasteUrlsAsPlainText` prop to control URL pasting behavior in `MarkdownEditor`
18 changes: 9 additions & 9 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
"dependencies": {
"@github/combobox-nav": "^2.1.5",
"@github/markdown-toolbar-element": "^2.1.0",
"@github/paste-markdown": "^1.3.1",
"@github/paste-markdown": "^1.4.0",
"@primer/behaviors": "^1.1.1",
"@primer/octicons-react": "^17.3.0",
"@primer/primitives": "7.9.0",
Expand Down
19 changes: 16 additions & 3 deletions src/drafts/MarkdownEditor/MarkdownEditor.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ const meta: Meta = {
'Hide Label',
'Required',
'Enable File Uploads',
'Enable Saved Replies'
'Enable Saved Replies',
'Enable Plain-Text URL Pasting'
]
}
},
Expand All @@ -57,6 +58,13 @@ const meta: Meta = {
type: 'boolean'
}
},
pasteUrlsAsPlainText: {
name: 'Enable Plain-Text URL Pasting',
defaultValue: false,
control: {
type: 'boolean'
}
},
minHeightLines: {
name: 'Minimum Height (Lines)',
defaultValue: 5,
Expand Down Expand Up @@ -122,6 +130,7 @@ type ArgProps = {
required: boolean
fileUploadsEnabled: boolean
savedRepliesEnabled: boolean
pasteUrlsAsPlainText: boolean
onSubmit: () => void
onDiffClick: () => void
}
Expand Down Expand Up @@ -200,7 +209,8 @@ export const Default = ({
required,
fileUploadsEnabled,
onSubmit,
savedRepliesEnabled
savedRepliesEnabled,
pasteUrlsAsPlainText
}: ArgProps) => {
const [value, setValue] = useState('')

Expand All @@ -223,6 +233,7 @@ export const Default = ({
referenceSuggestions={references}
savedReplies={savedRepliesEnabled ? savedReplies : undefined}
required={required}
pasteUrlsAsPlainText={pasteUrlsAsPlainText}
>
<MarkdownEditor.Label visuallyHidden={hideLabel}>Markdown Editor Example</MarkdownEditor.Label>
</MarkdownEditor>
Expand All @@ -242,7 +253,8 @@ export const CustomButtons = ({
fileUploadsEnabled,
onSubmit,
onDiffClick,
savedRepliesEnabled
savedRepliesEnabled,
pasteUrlsAsPlainText
}: ArgProps) => {
const [value, setValue] = useState('')

Expand All @@ -265,6 +277,7 @@ export const CustomButtons = ({
referenceSuggestions={references}
required={required}
savedReplies={savedRepliesEnabled ? savedReplies : undefined}
pasteUrlsAsPlainText={pasteUrlsAsPlainText}
>
<MarkdownEditor.Label visuallyHidden={hideLabel}>Markdown Editor Example</MarkdownEditor.Label>

Expand Down
31 changes: 31 additions & 0 deletions src/drafts/MarkdownEditor/MarkdownEditor.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {DiffAddedIcon} from '@primer/octicons-react'
import {fireEvent, render as _render, waitFor, within} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import {UserEvent} from '@testing-library/user-event/dist/types/setup'
import React, {forwardRef, useLayoutEffect, useRef, useState} from 'react'
import MarkdownEditor, {Emoji, MarkdownEditorHandle, MarkdownEditorProps, Mentionable, Reference, SavedReply} from '.'
import ThemeProvider from '../../ThemeProvider'
Expand Down Expand Up @@ -1140,4 +1141,34 @@ describe('MarkdownEditor', () => {
)
}
})

describe('pasting URLs', () => {
const typeAndPaste = async (user: UserEvent, input: HTMLTextAreaElement) => {
await user.type(input, 'lorem ipsum dolor sit amet')
input.setSelectionRange(6, 11)

// userEvent.paste() doesn't seem to fire the `paste` event that paste-markdown listens for.
// So we simulate it. This approach is somewhat fragile because it relies on the internals
// of paste-markdown not using any other properties on the event or DataTransfer instance.
// We can't just construct a `new DataTransfer` because that's not implemented in JSDOM.
fireEvent.paste(input, {clipboardData: {types: ['text/plain'], getData: () => 'https://github.com'}})
}

const linkifiedResult = 'lorem [ipsum](https://github.com) dolor sit amet'
const plainResult = 'lorem ipsum dolor sit amet' // the real-world plain text result should have "https://github.com" instead of "ipsum", but fireEvent.paste doesn't actually update the input value

it('pastes URLs onto selected text as links by default', async () => {
const {getInput, user} = await render(<UncontrolledEditor />)
const input = getInput()
await typeAndPaste(user, input)
expect(input).toHaveValue(linkifiedResult)
})

it('pastes URLs onto selected text as plain text when `pasteUrlsAsPlainText` enabled', async () => {
const {getInput, user} = await render(<UncontrolledEditor pasteUrlsAsPlainText />)
const input = getInput()
await typeAndPaste(user, input)
expect(input).toHaveValue(plainResult)
})
})
})
12 changes: 11 additions & 1 deletion src/drafts/MarkdownEditor/MarkdownEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ export type MarkdownEditorProps = SxProp & {
name?: string
/** To enable the saved replies feature, provide an array of replies. */
savedReplies?: SavedReply[]
/**
* Control whether URLs are pasted as plain text instead of as formatted links (if the
* user has selected some text before pasting). Defaults to `false` (URLs will paste as
* links). This should typically be controlled by user settings.
*
* Users can always toggle this behavior by holding `shift` when pasting.
*/
pasteUrlsAsPlainText?: boolean
}

const handleBrand = Symbol()
Expand Down Expand Up @@ -157,7 +165,8 @@ const MarkdownEditor = forwardRef<MarkdownEditorHandle, MarkdownEditorProps>(
required = false,
name,
children,
savedReplies
savedReplies,
pasteUrlsAsPlainText = false
},
ref
) => {
Expand Down Expand Up @@ -389,6 +398,7 @@ const MarkdownEditor = forwardRef<MarkdownEditorHandle, MarkdownEditorProps>(
monospace={monospace}
required={required}
name={name}
pasteUrlsAsPlainText={pasteUrlsAsPlainText}
{...inputCompositionProps}
{...fileHandler?.pasteTargetProps}
{...fileHandler?.dropTargetProps}
Expand Down
9 changes: 8 additions & 1 deletion src/drafts/MarkdownEditor/_MarkdownInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface MarkdownInputProps extends Omit<TextareaProps, 'onChange'> {
minHeightLines: number
maxHeightLines: number
monospace: boolean
pasteUrlsAsPlainText: boolean
/** Use this prop to control visibility instead of unmounting, so the undo stack and custom height are preserved. */
visible: boolean
}
Expand All @@ -47,6 +48,7 @@ export const MarkdownInput = forwardRef<HTMLTextAreaElement, MarkdownInputProps>
maxHeightLines,
visible,
monospace,
pasteUrlsAsPlainText,
...props
},
forwardedRef
Expand Down Expand Up @@ -81,7 +83,12 @@ export const MarkdownInput = forwardRef<HTMLTextAreaElement, MarkdownInputProps>
const ref = useRef<HTMLTextAreaElement>(null)
useRefObjectAsForwardedRef(forwardedRef, ref)

useEffect(() => (ref.current ? subscribeToMarkdownPasting(ref.current).unsubscribe : undefined), [])
useEffect(() => {
const subscription =
ref.current &&
subscribeToMarkdownPasting(ref.current, {defaultPlainTextPaste: {urlLinks: pasteUrlsAsPlainText}})
return subscription?.unsubscribe
}, [pasteUrlsAsPlainText])

const dynamicHeightStyles = useDynamicTextareaHeight({maxHeightLines, minHeightLines, element: ref.current, value})
const heightStyles = fullHeight ? {} : dynamicHeightStyles
Expand Down

0 comments on commit aeb6152

Please sign in to comment.