Skip to content

Commit 3a3a8ca

Browse files
authored
feat: retain links that GitHub treats as special
1 parent c00138b commit 3a3a8ca

File tree

2 files changed

+78
-2
lines changed

2 files changed

+78
-2
lines changed

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: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,43 @@ 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+
// eslint-disable-next-line github/unescaped-html-literal
179+
const href = 'https://github.com/octocat/repo/issues/1'
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-next-line i18n-text/no-en
196+
const plaintextSentence = 'This is a https://github.com and another link\n\nLink at the beginning, link at the https://github.com/'
197+
const markdownSentence =
198+
'This is a https://github.com/ and [another link](https://www.youtube.com/watch?v=dQw4w9WgXcQ)\n\n' +
199+
'[Link](https://github.com/) at the beginning, link at the https://github.com/'
200+
201+
paste(textarea, {'text/html': sentence, 'text/plain': plaintextSentence})
202+
assert.equal(textarea.value, markdownSentence)
203+
})
167204
})
168205
})
169206

0 commit comments

Comments
 (0)