Skip to content

Commit 5e963dc

Browse files
authored
Show checkmark on Copy as Markdown button after copy (#61954)
1 parent 64c6022 commit 5e963dc

2 files changed

Lines changed: 54 additions & 2 deletions

File tree

src/fixtures/tests/playwright-rendering.spec.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1534,3 +1534,38 @@ test.describe('Non-child page resolution', () => {
15341534
// The detailed sidebar filtering is tested by the survey test which verifies no duplicate entries
15351535
})
15361536
})
1537+
1538+
test.describe('copy as markdown button', () => {
1539+
// The article-body fetch backing this button is served for this fixture page
1540+
// (see src/fixtures/tests/api-article-body.ts), so the copy path succeeds.
1541+
const articlePath = '/en/get-started/start-your-journey/api-article-body-test-page'
1542+
1543+
test('swaps the copy icon for a checkmark after a successful copy', async ({ page, context }) => {
1544+
// The click handler writes the article markdown to the clipboard.
1545+
await context.grantPermissions(['clipboard-read', 'clipboard-write'])
1546+
1547+
await page.goto(articlePath)
1548+
await turnOffExperimentsInPage(page)
1549+
1550+
const copyButton = page.getByRole('button', { name: 'Copy as Markdown' })
1551+
await expect(copyButton).toBeVisible()
1552+
1553+
// Before clicking, the leading icon is the copy icon, not the checkmark.
1554+
await expect(copyButton.locator('.octicon-copy')).toBeVisible()
1555+
await expect(copyButton.locator('.octicon-check')).toHaveCount(0)
1556+
1557+
await copyButton.click()
1558+
1559+
// After a successful copy, the icon swaps to a checkmark...
1560+
await expect(copyButton.locator('.octicon-check')).toBeVisible()
1561+
await expect(copyButton.locator('.octicon-copy')).toHaveCount(0)
1562+
1563+
// ...and the article markdown lands on the clipboard.
1564+
const clipboardText = await page.evaluate(() => navigator.clipboard.readText())
1565+
expect(clipboardText).toContain('About GitHub')
1566+
1567+
// The checkmark is temporary and reverts to the copy icon (2s timeout).
1568+
await expect(copyButton.locator('.octicon-copy')).toBeVisible({ timeout: 5000 })
1569+
await expect(copyButton.locator('.octicon-check')).toHaveCount(0)
1570+
})
1571+
})

src/frame/components/article/ViewMarkdownButton.tsx

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { useCallback } from 'react'
1+
import { useCallback, useEffect, useRef, useState } from 'react'
22
import {
3+
CheckIcon,
34
CopyIcon,
45
CopilotIcon,
56
FileIcon,
@@ -22,6 +23,15 @@ interface CopyMarkdownMenuProps {
2223
export const CopyMarkdownMenu = ({ currentPath }: CopyMarkdownMenuProps) => {
2324
const { t } = useTranslation('pages')
2425

26+
const [copied, setCopied] = useState(false)
27+
const timeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null)
28+
29+
useEffect(() => {
30+
return () => {
31+
if (timeoutRef.current) clearTimeout(timeoutRef.current)
32+
}
33+
}, [])
34+
2535
const encodedPath = encodeURIComponent(currentPath).replace(/%2F/g, '/').replace(/%40/g, '@')
2636
const markdownUrl = `/api/article/body?pathname=${encodedPath}`
2737

@@ -64,6 +74,9 @@ export const CopyMarkdownMenu = ({ currentPath }: CopyMarkdownMenuProps) => {
6474
const text = await res.text()
6575
await navigator.clipboard.writeText(text)
6676
announce(t('copied'))
77+
setCopied(true)
78+
if (timeoutRef.current) clearTimeout(timeoutRef.current)
79+
timeoutRef.current = setTimeout(() => setCopied(false), 2000)
6780
} catch {
6881
// Fallback: open in new tab if fetch or clipboard fails
6982
window.open(markdownUrl, '_blank')
@@ -81,7 +94,11 @@ export const CopyMarkdownMenu = ({ currentPath }: CopyMarkdownMenuProps) => {
8194
)}
8295
onClick={handleCopyClick}
8396
>
84-
<CopyIcon size={12} className="mr-1" aria-hidden="true" />
97+
{copied ? (
98+
<CheckIcon size={12} className="mr-1" aria-hidden="true" />
99+
) : (
100+
<CopyIcon size={12} className="mr-1" aria-hidden="true" />
101+
)}
85102
{t('copy_as_markdown')}
86103
</Button>
87104
<ActionMenu>

0 commit comments

Comments
 (0)