Skip to content

Commit 7cccbc0

Browse files
authored
Merge pull request #39 from imjohnbo/imjohnbo/fix-github-paste
Fix "special" link paste in GitHub
2 parents 7d7acff + c010941 commit 7cccbc0

File tree

3 files changed

+82
-3
lines changed

3 files changed

+82
-3
lines changed

.eslintrc.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@
1212
"rules": {
1313
"import/extensions": "off",
1414
"import/no-unresolved": "off",
15-
"github/no-inner-html": "off"
15+
"github/no-inner-html": "off",
16+
"eslint-comments/no-use": "off"
1617
}
1718
}
1819
]

src/paste-markdown-html.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,16 @@ function onPaste(event: ClipboardEvent) {
2222
// Get the plaintext and html version of clipboard contents
2323
let text = transfer.getData('text/plain')
2424
const textHTML = transfer.getData('text/html')
25+
// Replace Unicode equivalent of "&nbsp" with a space
26+
const textHTMLClean = textHTML.replace(/\u00A0/g, ' ')
2527
if (!textHTML) return
2628

2729
text = text.trim()
2830
if (!text) return
2931

3032
// Generate DOM tree from HTML string
3133
const parser = new DOMParser()
32-
const doc = parser.parseFromString(textHTML, 'text/html')
34+
const doc = parser.parseFromString(textHTMLClean, 'text/html')
3335

3436
const a = doc.getElementsByTagName('a')
3537
const markdown = transform(a, text, linkify as MarkdownTransformer)
@@ -83,6 +85,43 @@ function hasHTML(transfer: DataTransfer): boolean {
8385
return transfer.types.includes('text/html')
8486
}
8587

88+
// Makes markdown link from a link element, avoiding special GitHub links
8689
function linkify(element: HTMLAnchorElement): string {
87-
return `[${element.textContent}](${element.href})`
90+
const label = element.textContent || ''
91+
const url = element.href || ''
92+
let markdown = ''
93+
94+
// Don't linkify user mentions like "@octocat"
95+
if (isUserMention(element)) {
96+
markdown = label
97+
// Don't linkify things like "#123" or commit comparisons
98+
} else if (isSpecialLink(element) || areEqualLinks(url, label)) {
99+
markdown = url
100+
// Otherwise, make a markdown link
101+
} else {
102+
markdown = `[${label}](${url})`
103+
}
104+
105+
return markdown
106+
}
107+
108+
// Special GitHub links have either a hover card or certain class name
109+
function isSpecialLink(link: HTMLAnchorElement): boolean {
110+
return (
111+
link.className.indexOf('commit-link') >= 0 ||
112+
(!!link.getAttribute('data-hovercard-type') && link.getAttribute('data-hovercard-type') !== 'user')
113+
)
114+
}
115+
116+
// Browsers sometimes copy a stray "/" at the end of a link
117+
// Also, unequal string casing shouldn't disqualify links from being equal
118+
function areEqualLinks(link1: string, link2: string) {
119+
link1 = link1.slice(-1) === '/' ? link1.slice(0, -1) : link1
120+
link2 = link2.slice(-1) === '/' ? link2.slice(0, -1) : link2
121+
return link1.toLowerCase() === link2.toLowerCase()
122+
}
123+
124+
// User mentions have a "@" and a hovercard attribute of type "user"
125+
function isUserMention(link: HTMLAnchorElement): boolean {
126+
return link.textContent?.slice(0, 1) === '@' && link.getAttribute('data-hovercard-type') === 'user'
88127
}

test/test.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,45 @@ describe('paste-markdown', function () {
164164
paste(textarea, {'text/html': link, 'text/plain': plaintextLink, 'text/link-preview': linkPreviewLink})
165165
assert.equal(textarea.value, '')
166166
})
167+
168+
it("doesn't render any markdown for GitHub handles", function () {
169+
// eslint-disable-next-line github/unescaped-html-literal
170+
const link = `<meta charset='utf-8'><a href="https://github.com/octocat" data-hovercard-type="user">@octocat</a>`
171+
const plaintextLink = '@octocat'
172+
173+
paste(textarea, {'text/html': link, 'text/plain': plaintextLink})
174+
assert.equal(textarea.value, '')
175+
})
176+
177+
it('retains urls of special GitHub links', function () {
178+
const href = 'https://github.com/octocat/repo/issues/1'
179+
// eslint-disable-next-line github/unescaped-html-literal
180+
const link = `<meta charset='utf-8'><a href=${href} data-hovercard-type="issue">#1</a>`
181+
const plaintextLink = '#1'
182+
183+
paste(textarea, {'text/html': link, 'text/plain': plaintextLink})
184+
assert.equal(textarea.value, href)
185+
})
186+
187+
it('leaves plaintext links alone', function () {
188+
// eslint-disable-next-line github/unescaped-html-literal
189+
const sentence = `<meta charset='utf-8'><meta charset="utf-8">
190+
<b style="font-weight:normal;"><p dir="ltr"><span>This is a </span>
191+
<a href="https://github.com/"><span>https://github.com</span></a><span> and </span>
192+
<a href="https://www.youtube.com/watch?v=dQw4w9WgXcQ"><span>another link</span></a></p>
193+
<br /><a href="https://github.com/"><span>Link</span></a><span> at the beginning, link at the </span>
194+
<a href="https://github.com/"><span>https://github.com/</span></a></b>`
195+
/* eslint-disable i18n-text/no-en */
196+
const plaintextSentence =
197+
'This is a https://github.com and another link\n\nLink at the beginning, link at the https://github.com/'
198+
/* eslint-enable i18n-text/no-en */
199+
const markdownSentence =
200+
'This is a https://github.com/ and [another link](https://www.youtube.com/watch?v=dQw4w9WgXcQ)\n\n' +
201+
'[Link](https://github.com/) at the beginning, link at the https://github.com/'
202+
203+
paste(textarea, {'text/html': sentence, 'text/plain': plaintextSentence})
204+
assert.equal(textarea.value, markdownSentence)
205+
})
167206
})
168207
})
169208

0 commit comments

Comments
 (0)