Skip to content

Commit d69c5b6

Browse files
authored
Merge pull request #6 from github/quote-on-copy
Quote Markdown text on clipboard copy
2 parents c3f5cec + 8e5dd7c commit d69c5b6

1 file changed

Lines changed: 61 additions & 20 deletions

File tree

quote-selection.js

Lines changed: 61 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export function install(container: Element) {
2222
installed += containers.has(container) ? 0 : 1
2323
containers.set(container, 1)
2424
document.addEventListener('keydown', quoteSelection)
25+
container.addEventListener('copy', onCopy)
2526
}
2627

2728
export function uninstall(container: Element) {
@@ -30,6 +31,29 @@ export function uninstall(container: Element) {
3031
if (!installed) {
3132
document.removeEventListener('keydown', quoteSelection)
3233
}
34+
container.removeEventListener('copy', onCopy)
35+
}
36+
37+
function onCopy(event: ClipboardEvent) {
38+
const target = event.target
39+
if (!(target instanceof HTMLElement)) return
40+
if (isFormField(target)) return
41+
42+
const transfer = event.clipboardData
43+
if (!transfer) return
44+
45+
const selection = window.getSelection()
46+
let range
47+
try {
48+
range = selection.getRangeAt(0)
49+
} catch (err) {
50+
return
51+
}
52+
const quoted = extractQuote(selection.toString(), range)
53+
if (!quoted) return
54+
55+
transfer.setData('text/plain', quoted.selectionText)
56+
event.preventDefault()
3357
}
3458

3559
function eventIsNotRelevant(event: KeyboardEvent): boolean {
@@ -72,17 +96,47 @@ function quoteSelection(event: KeyboardEvent): void {
7296
}
7397

7498
export function quote(text: string, range: Range): boolean {
99+
const quoted = extractQuote(text, range)
100+
if (!quoted) return false
101+
102+
const {container, selectionText} = quoted
103+
104+
const dispatched = container.dispatchEvent(
105+
new CustomEvent('quote-selection', {
106+
bubbles: true,
107+
cancelable: true,
108+
detail: {range, selectionText}
109+
})
110+
)
111+
112+
if (!dispatched) {
113+
return true
114+
}
115+
116+
const field = findTextarea(container)
117+
if (!field) return false
118+
119+
insertQuote(selectionText, field)
120+
return true
121+
}
122+
123+
type Quote = {
124+
container: Element,
125+
selectionText: string
126+
}
127+
128+
function extractQuote(text: string, range: Range): ?Quote {
75129
let selectionText = text.trim()
76-
if (!selectionText) return false
130+
if (!selectionText) return
77131

78132
let focusNode = range.startContainer
79-
if (!focusNode) return false
133+
if (!focusNode) return
80134

81135
if (focusNode.nodeType !== Node.ELEMENT_NODE) focusNode = focusNode.parentNode
82-
if (!(focusNode instanceof Element)) return false
136+
if (!(focusNode instanceof Element)) return
83137

84138
const container = findContainer(focusNode)
85-
if (!container) return false
139+
if (!container) return
86140

87141
const markdownSelector = container.getAttribute('data-quote-markdown')
88142
if (markdownSelector != null) {
@@ -97,21 +151,10 @@ export function quote(text: string, range: Range): boolean {
97151
}
98152
}
99153

100-
const dispatched = container.dispatchEvent(
101-
new CustomEvent('quote-selection', {
102-
bubbles: true,
103-
cancelable: true,
104-
detail: {range, selectionText}
105-
})
106-
)
107-
108-
if (!dispatched) {
109-
return true
110-
}
111-
112-
const field = findTextarea(container)
113-
if (!field) return false
154+
return {selectionText, container}
155+
}
114156

157+
function insertQuote(selectionText: string, field: HTMLTextAreaElement) {
115158
let quotedText = `> ${selectionText.replace(/\n/g, '\n> ')}\n\n`
116159
if (field.value) {
117160
quotedText = `${field.value}\n\n${quotedText}`
@@ -120,8 +163,6 @@ export function quote(text: string, range: Range): boolean {
120163
field.focus()
121164
field.selectionStart = field.value.length
122165
field.scrollTop = field.scrollHeight
123-
124-
return true
125166
}
126167

127168
function visible(el: HTMLElement): boolean {

0 commit comments

Comments
 (0)