Handle GitHub attachment image load failures (#6759)

This commit is contained in:
mehmet turac 2026-07-04 03:32:08 +03:00 committed by GitHub
parent 69415946dd
commit ca36072295
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 119 additions and 2 deletions

View File

@ -0,0 +1,64 @@
// @vitest-environment happy-dom
import { act } from 'react'
import { createRoot, type Root } from 'react-dom/client'
import { afterEach, beforeEach, describe, expect, it } from 'vitest'
import CommentMarkdown from './CommentMarkdown'
const attachmentUrl =
'https://github.com/user-attachments/assets/ce11040a-fb66-4289-927f-547b16dfc488'
let root: Root | null = null
let container: HTMLDivElement | null = null
function renderCommentMarkdown(content: string): HTMLDivElement {
container = document.createElement('div')
document.body.appendChild(container)
root = createRoot(container)
act(() => {
root?.render(<CommentMarkdown variant="document" content={content} />)
})
return container
}
describe('CommentMarkdown GitHub attachment images', () => {
beforeEach(() => {
globalThis.IS_REACT_ACT_ENVIRONMENT = true
})
afterEach(() => {
if (root) {
act(() => root?.unmount())
}
document.body.replaceChildren()
root = null
container = null
})
it('renders GitHub user attachment document images as openable links', () => {
const mounted = renderCommentMarkdown(`![Private issue screenshot](${attachmentUrl})`)
const link = mounted.querySelector<HTMLAnchorElement>(`a[href="${attachmentUrl}"]`)
const image = link?.querySelector<HTMLImageElement>('img')
expect(link).not.toBeNull()
expect(link?.className).toContain('inline-block')
expect(image?.src).toBe(attachmentUrl)
expect(image?.alt).toBe('Private issue screenshot')
})
it('falls back to a text link when a GitHub user attachment image cannot load', () => {
const mounted = renderCommentMarkdown(`![Private issue screenshot](${attachmentUrl})`)
const image = mounted.querySelector<HTMLImageElement>(`img[src="${attachmentUrl}"]`)
expect(image).not.toBeNull()
act(() => {
image?.dispatchEvent(new window.Event('error'))
})
expect(mounted.querySelector(`img[src="${attachmentUrl}"]`)).toBeNull()
const fallback = mounted.querySelector<HTMLAnchorElement>(`a[href="${attachmentUrl}"]`)
expect(fallback?.textContent).toBe('Private issue screenshot')
expect(fallback?.className).toContain('underline')
})
})

View File

@ -2,9 +2,11 @@ import React from 'react'
import type { Components } from 'react-markdown'
import { isMermaidFence, isMermaidPre, renderMermaidFence } from './comment-mermaid-fence'
import {
GitHubUserAttachmentImage,
GitHubUserAttachmentVideo,
isGitHubUserAttachmentUrl,
isGitHubUserAttachmentVideoLink
} from './comment-markdown-github-attachment-video'
} from './comment-markdown-github-attachment-media'
export type CommentMarkdownLinkClickHandler = (
event: React.MouseEvent<HTMLElement>,
@ -230,6 +232,12 @@ export function createDocumentCommentMarkdownComponents(
</blockquote>
),
img: ({ alt, src }) => {
if (isGitHubUserAttachmentUrl(src)) {
// Why: private-repo attachment images fail as cross-origin loads; a
// top-level link opens them in a GitHub-authenticated tab, and falls
// back to a text link when the image itself can't render.
return <GitHubUserAttachmentImage src={src} alt={alt} />
}
const imageClassName = [
'my-3 max-h-96 max-w-full rounded-md object-contain',
'outline outline-1 outline-black/10 dark:outline-white/10',

View File

@ -1,6 +1,6 @@
import React from 'react'
function isGitHubUserAttachmentUrl(href: string | undefined): href is string {
export function isGitHubUserAttachmentUrl(href: string | undefined): href is string {
if (!href) {
return false
}
@ -67,3 +67,48 @@ export function GitHubUserAttachmentVideo({
</video>
)
}
export function GitHubUserAttachmentImage({
src,
alt
}: {
src: string
alt: string | undefined
}): React.ReactElement {
const [failed, setFailed] = React.useState(false)
const label = alt?.trim() || src
// Why: private-repo attachment images can't load cross-origin without the
// user's GitHub session cookies, so wrap in a top-level link (opening the
// URL where that session exists) and drop to a text link on load error.
if (failed) {
return (
<a
href={src}
target="_blank"
rel="noreferrer"
className="break-all text-primary underline underline-offset-2 hover:text-primary/80"
onClick={(e) => e.stopPropagation()}
>
{label}
</a>
)
}
return (
<a
href={src}
target="_blank"
rel="noreferrer"
className="inline-block max-w-full"
onClick={(e) => e.stopPropagation()}
>
<img
src={src}
alt={alt ?? ''}
className="my-3 max-h-96 max-w-full rounded-md object-contain outline outline-1 outline-black/10 dark:outline-white/10"
onError={() => setFailed(true)}
/>
</a>
)
}