Handle GitHub attachment image load failures (#6759)
This commit is contained in:
parent
69415946dd
commit
ca36072295
|
|
@ -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(``)
|
||||
|
||||
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(``)
|
||||
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')
|
||||
})
|
||||
})
|
||||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
Loading…
Reference in New Issue