From ca36072295fb6d8167fa321dae5521fbcef0ef03 Mon Sep 17 00:00:00 2001 From: mehmet turac Date: Sat, 4 Jul 2026 03:32:08 +0300 Subject: [PATCH] Handle GitHub attachment image load failures (#6759) --- ...tMarkdown.github-attachment-image.test.tsx | 64 +++++++++++++++++++ .../comment-markdown-element-renderers.tsx | 10 ++- ...ment-markdown-github-attachment-media.tsx} | 47 +++++++++++++- 3 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 src/renderer/src/components/sidebar/CommentMarkdown.github-attachment-image.test.tsx rename src/renderer/src/components/sidebar/{comment-markdown-github-attachment-video.tsx => comment-markdown-github-attachment-media.tsx} (55%) diff --git a/src/renderer/src/components/sidebar/CommentMarkdown.github-attachment-image.test.tsx b/src/renderer/src/components/sidebar/CommentMarkdown.github-attachment-image.test.tsx new file mode 100644 index 000000000..49bf77d7e --- /dev/null +++ b/src/renderer/src/components/sidebar/CommentMarkdown.github-attachment-image.test.tsx @@ -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() + }) + 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(`a[href="${attachmentUrl}"]`) + const image = link?.querySelector('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(`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(`a[href="${attachmentUrl}"]`) + expect(fallback?.textContent).toBe('Private issue screenshot') + expect(fallback?.className).toContain('underline') + }) +}) diff --git a/src/renderer/src/components/sidebar/comment-markdown-element-renderers.tsx b/src/renderer/src/components/sidebar/comment-markdown-element-renderers.tsx index 5f2e0b972..7940d6278 100644 --- a/src/renderer/src/components/sidebar/comment-markdown-element-renderers.tsx +++ b/src/renderer/src/components/sidebar/comment-markdown-element-renderers.tsx @@ -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, @@ -230,6 +232,12 @@ export function createDocumentCommentMarkdownComponents( ), 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 + } const imageClassName = [ 'my-3 max-h-96 max-w-full rounded-md object-contain', 'outline outline-1 outline-black/10 dark:outline-white/10', diff --git a/src/renderer/src/components/sidebar/comment-markdown-github-attachment-video.tsx b/src/renderer/src/components/sidebar/comment-markdown-github-attachment-media.tsx similarity index 55% rename from src/renderer/src/components/sidebar/comment-markdown-github-attachment-video.tsx rename to src/renderer/src/components/sidebar/comment-markdown-github-attachment-media.tsx index 448493389..7f94e907e 100644 --- a/src/renderer/src/components/sidebar/comment-markdown-github-attachment-video.tsx +++ b/src/renderer/src/components/sidebar/comment-markdown-github-attachment-media.tsx @@ -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({ ) } + +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 ( + e.stopPropagation()} + > + {label} + + ) + } + + return ( + e.stopPropagation()} + > + {alt setFailed(true)} + /> + + ) +}