From 1e537420d511153ef72d18eff76f1227ae33be5b Mon Sep 17 00:00:00 2001 From: tmimmanuel <14046872+tmimmanuel@users.noreply.github.com> Date: Fri, 20 Mar 2026 20:31:45 +0100 Subject: [PATCH 1/6] fix: readme relative links --- .../repositories/ContributingViewer.tsx | 33 ++++++++++++++++++- src/components/repositories/ReadmeViewer.tsx | 33 +++++++++++++++++++ 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/src/components/repositories/ContributingViewer.tsx b/src/components/repositories/ContributingViewer.tsx index cc3b110e..c952442d 100644 --- a/src/components/repositories/ContributingViewer.tsx +++ b/src/components/repositories/ContributingViewer.tsx @@ -81,6 +81,37 @@ const ContributingViewer: React.FC = ({ ); } + // Custom renderer for links to handle relative paths + const LinkRenderer = (props: any) => { + const { href, children, ...rest } = props; + let finalHref = href; + + if ( + href && + !href.startsWith('http') && + !href.startsWith('//') && + !href.startsWith('#') && + !href.startsWith('mailto:') + ) { + const cleanPath = href.startsWith('./') + ? href.slice(2) + : href.startsWith('/') + ? href.slice(1) + : href; + const hasExtension = /\.[a-zA-Z0-9]+$/.test(cleanPath.replace(/\/$/, '')); + const isDirectory = cleanPath.endsWith('/') || !hasExtension; + const type = isDirectory ? 'tree' : 'blob'; + const normalizedPath = cleanPath.replace(/\/$/, ''); + finalHref = `https://github.com/${repositoryFullName}/${type}/${defaultBranch}/${normalizedPath}`; + } + + return ( + + {children} + + ); + }; + // Custom renderer for images to handle relative paths const ImageRenderer = (props: any) => { const { src, alt, ...rest } = props; @@ -204,7 +235,7 @@ const ContributingViewer: React.FC = ({ {content || ''} diff --git a/src/components/repositories/ReadmeViewer.tsx b/src/components/repositories/ReadmeViewer.tsx index 7dcee7b9..9f3e1f0d 100644 --- a/src/components/repositories/ReadmeViewer.tsx +++ b/src/components/repositories/ReadmeViewer.tsx @@ -68,6 +68,38 @@ const ReadmeViewer: React.FC = ({ repositoryFullName }) => { ); } + // Custom renderer for links to handle relative paths + const LinkRenderer = (props: any) => { + const { href, children, ...rest } = props; + let finalHref = href; + + if ( + href && + !href.startsWith('http') && + !href.startsWith('//') && + !href.startsWith('#') && + !href.startsWith('mailto:') + ) { + const cleanPath = href.startsWith('./') + ? href.slice(2) + : href.startsWith('/') + ? href.slice(1) + : href; + // Use /tree/ for directories (no extension or ends with /), /blob/ for files + const hasExtension = /\.[a-zA-Z0-9]+$/.test(cleanPath.replace(/\/$/, '')); + const isDirectory = cleanPath.endsWith('/') || !hasExtension; + const type = isDirectory ? 'tree' : 'blob'; + const normalizedPath = cleanPath.replace(/\/$/, ''); + finalHref = `https://github.com/${repositoryFullName}/${type}/${defaultBranch}/${normalizedPath}`; + } + + return ( + + {children} + + ); + }; + // Custom renderer for images to handle relative paths const ImageRenderer = (props: any) => { const { src, alt, ...rest } = props; @@ -209,6 +241,7 @@ const ReadmeViewer: React.FC = ({ repositoryFullName }) => { remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} components={{ + a: LinkRenderer, img: ImageRenderer, }} > From b72f73892b9341fce2901e69ca9adc0c4db3d587 Mon Sep 17 00:00:00 2001 From: tmimmanuel <14046872+tmimmanuel@users.noreply.github.com> Date: Mon, 30 Mar 2026 23:48:48 +0200 Subject: [PATCH 2/6] fix: extract shared LinkRenderer and ImageRenderer into MarkdownRenderers.tsx --- .../repositories/ContributingViewer.tsx | 62 +----------------- .../repositories/MarkdownRenderers.tsx | 65 +++++++++++++++++++ src/components/repositories/ReadmeViewer.tsx | 65 +------------------ 3 files changed, 71 insertions(+), 121 deletions(-) create mode 100644 src/components/repositories/MarkdownRenderers.tsx diff --git a/src/components/repositories/ContributingViewer.tsx b/src/components/repositories/ContributingViewer.tsx index c952442d..b8ce82b3 100644 --- a/src/components/repositories/ContributingViewer.tsx +++ b/src/components/repositories/ContributingViewer.tsx @@ -5,6 +5,7 @@ import remarkGfm from 'remark-gfm'; import rehypeRaw from 'rehype-raw'; import axios from 'axios'; import { STATUS_COLORS } from '../../theme'; +import { createLinkRenderer, createImageRenderer } from './MarkdownRenderers'; interface ContributingViewerProps { repositoryFullName: string; // e.g., "opentensor/bittensor" @@ -81,65 +82,8 @@ const ContributingViewer: React.FC = ({ ); } - // Custom renderer for links to handle relative paths - const LinkRenderer = (props: any) => { - const { href, children, ...rest } = props; - let finalHref = href; - - if ( - href && - !href.startsWith('http') && - !href.startsWith('//') && - !href.startsWith('#') && - !href.startsWith('mailto:') - ) { - const cleanPath = href.startsWith('./') - ? href.slice(2) - : href.startsWith('/') - ? href.slice(1) - : href; - const hasExtension = /\.[a-zA-Z0-9]+$/.test(cleanPath.replace(/\/$/, '')); - const isDirectory = cleanPath.endsWith('/') || !hasExtension; - const type = isDirectory ? 'tree' : 'blob'; - const normalizedPath = cleanPath.replace(/\/$/, ''); - finalHref = `https://github.com/${repositoryFullName}/${type}/${defaultBranch}/${normalizedPath}`; - } - - return ( - - {children} - - ); - }; - - // Custom renderer for images to handle relative paths - const ImageRenderer = (props: any) => { - const { src, alt, ...rest } = props; - let finalSrc = src; - - if (src && !src.startsWith('http') && !src.startsWith('//')) { - const cleanPath = src.startsWith('./') - ? src.slice(2) - : src.startsWith('/') - ? src.slice(1) - : src; - finalSrc = `https://cdn.jsdelivr.net/gh/${repositoryFullName}@${defaultBranch}/${cleanPath}`; - } - - return ( - {alt} - ); - }; + const LinkRenderer = createLinkRenderer(repositoryFullName, defaultBranch); + const ImageRenderer = createImageRenderer(repositoryFullName, defaultBranch); return ( { + const LinkRenderer = (props: any) => { + const { href, children, ...rest } = props; + let finalHref = href; + + if ( + href && + !href.startsWith('http') && + !href.startsWith('//') && + !href.startsWith('#') && + !href.startsWith('mailto:') + ) { + const cleanPath = href.startsWith('./') + ? href.slice(2) + : href.startsWith('/') + ? href.slice(1) + : href; + const hasExtension = /\.[a-zA-Z0-9]+$/.test(cleanPath.replace(/\/$/, '')); + const isDirectory = cleanPath.endsWith('/') || !hasExtension; + const type = isDirectory ? 'tree' : 'blob'; + const normalizedPath = cleanPath.replace(/\/$/, ''); + finalHref = `https://github.com/${repositoryFullName}/${type}/${defaultBranch}/${normalizedPath}`; + } + + return ( + + {children} + + ); + }; + return LinkRenderer; +}; + +export const createImageRenderer = (repositoryFullName: string, defaultBranch: string) => { + const ImageRenderer = (props: any) => { + const { src, alt, ...rest } = props; + let finalSrc = src; + + if (src && !src.startsWith('http') && !src.startsWith('//')) { + const cleanPath = src.startsWith('./') + ? src.slice(2) + : src.startsWith('/') + ? src.slice(1) + : src; + finalSrc = `https://cdn.jsdelivr.net/gh/${repositoryFullName}@${defaultBranch}/${cleanPath}`; + } + + return ( + {alt} + ); + }; + return ImageRenderer; +}; diff --git a/src/components/repositories/ReadmeViewer.tsx b/src/components/repositories/ReadmeViewer.tsx index 9f3e1f0d..7a7fa8d2 100644 --- a/src/components/repositories/ReadmeViewer.tsx +++ b/src/components/repositories/ReadmeViewer.tsx @@ -5,6 +5,7 @@ import remarkGfm from 'remark-gfm'; import rehypeRaw from 'rehype-raw'; import axios from 'axios'; import { STATUS_COLORS } from '../../theme'; +import { createLinkRenderer, createImageRenderer } from './MarkdownRenderers'; interface ReadmeViewerProps { repositoryFullName: string; // e.g., "opentensor/bittensor" @@ -68,68 +69,8 @@ const ReadmeViewer: React.FC = ({ repositoryFullName }) => { ); } - // Custom renderer for links to handle relative paths - const LinkRenderer = (props: any) => { - const { href, children, ...rest } = props; - let finalHref = href; - - if ( - href && - !href.startsWith('http') && - !href.startsWith('//') && - !href.startsWith('#') && - !href.startsWith('mailto:') - ) { - const cleanPath = href.startsWith('./') - ? href.slice(2) - : href.startsWith('/') - ? href.slice(1) - : href; - // Use /tree/ for directories (no extension or ends with /), /blob/ for files - const hasExtension = /\.[a-zA-Z0-9]+$/.test(cleanPath.replace(/\/$/, '')); - const isDirectory = cleanPath.endsWith('/') || !hasExtension; - const type = isDirectory ? 'tree' : 'blob'; - const normalizedPath = cleanPath.replace(/\/$/, ''); - finalHref = `https://github.com/${repositoryFullName}/${type}/${defaultBranch}/${normalizedPath}`; - } - - return ( - - {children} - - ); - }; - - // Custom renderer for images to handle relative paths - const ImageRenderer = (props: any) => { - const { src, alt, ...rest } = props; - let finalSrc = src; - - if (src && !src.startsWith('http') && !src.startsWith('//')) { - // Convert relative path to absolute GitHub user content path - // e.g. ./assets/img.png -> https://raw.githubusercontent.com/user/repo/branch/assets/img.png - const cleanPath = src.startsWith('./') - ? src.slice(2) - : src.startsWith('/') - ? src.slice(1) - : src; - finalSrc = `https://cdn.jsdelivr.net/gh/${repositoryFullName}@${defaultBranch}/${cleanPath}`; - } - - return ( - {alt} - ); - }; + const LinkRenderer = createLinkRenderer(repositoryFullName, defaultBranch); + const ImageRenderer = createImageRenderer(repositoryFullName, defaultBranch); return ( Date: Tue, 31 Mar 2026 01:08:50 +0200 Subject: [PATCH 3/6] fix: add MarkdownRenderers in index.ts --- src/components/repositories/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/repositories/index.ts b/src/components/repositories/index.ts index eb2b16a3..715b6323 100644 --- a/src/components/repositories/index.ts +++ b/src/components/repositories/index.ts @@ -11,3 +11,4 @@ export { default as ReadmeViewer } from './ReadmeViewer'; export { default as RepositoryCodeBrowser } from './RepositoryCodeBrowser'; export { default as RepositoryMaintainers } from './RepositoryMaintainers'; export { default as RepositoryCheckTab } from './RepositoryCheckTab'; +export { createLinkRenderer, createImageRenderer } from './MarkdownRenderers'; From 092237a9a69eecc808d2e98c9120467dc99c8f2f Mon Sep 17 00:00:00 2001 From: tmimmanuel <14046872+tmimmanuel@users.noreply.github.com> Date: Wed, 1 Apr 2026 17:27:33 +0200 Subject: [PATCH 4/6] fix: lint error --- src/components/repositories/MarkdownRenderers.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/components/repositories/MarkdownRenderers.tsx b/src/components/repositories/MarkdownRenderers.tsx index 1ddc55e3..0f328a03 100644 --- a/src/components/repositories/MarkdownRenderers.tsx +++ b/src/components/repositories/MarkdownRenderers.tsx @@ -1,6 +1,9 @@ import React from 'react'; -export const createLinkRenderer = (repositoryFullName: string, defaultBranch: string) => { +export const createLinkRenderer = ( + repositoryFullName: string, + defaultBranch: string, +) => { const LinkRenderer = (props: any) => { const { href, children, ...rest } = props; let finalHref = href; @@ -33,7 +36,10 @@ export const createLinkRenderer = (repositoryFullName: string, defaultBranch: st return LinkRenderer; }; -export const createImageRenderer = (repositoryFullName: string, defaultBranch: string) => { +export const createImageRenderer = ( + repositoryFullName: string, + defaultBranch: string, +) => { const ImageRenderer = (props: any) => { const { src, alt, ...rest } = props; let finalSrc = src; From e67f15b844423984759bcabfddf1b88c7eff67c9 Mon Sep 17 00:00:00 2001 From: tmimmanuel <14046872+tmimmanuel@users.noreply.github.com> Date: Thu, 9 Apr 2026 17:26:50 +0200 Subject: [PATCH 5/6] fix: simplify markdown renderers to a single resolveRelativeUrl helper --- .../repositories/ContributingViewer.tsx | 21 +++-- .../repositories/MarkdownRenderers.tsx | 83 ++++--------------- src/components/repositories/ReadmeViewer.tsx | 24 ++++-- src/components/repositories/index.ts | 2 +- 4 files changed, 51 insertions(+), 79 deletions(-) diff --git a/src/components/repositories/ContributingViewer.tsx b/src/components/repositories/ContributingViewer.tsx index b8ce82b3..e240f12c 100644 --- a/src/components/repositories/ContributingViewer.tsx +++ b/src/components/repositories/ContributingViewer.tsx @@ -5,7 +5,7 @@ import remarkGfm from 'remark-gfm'; import rehypeRaw from 'rehype-raw'; import axios from 'axios'; import { STATUS_COLORS } from '../../theme'; -import { createLinkRenderer, createImageRenderer } from './MarkdownRenderers'; +import { resolveRelativeUrl } from './MarkdownRenderers'; interface ContributingViewerProps { repositoryFullName: string; // e.g., "opentensor/bittensor" @@ -82,9 +82,6 @@ const ContributingViewer: React.FC = ({ ); } - const LinkRenderer = createLinkRenderer(repositoryFullName, defaultBranch); - const ImageRenderer = createImageRenderer(repositoryFullName, defaultBranch); - return ( = ({ ( + + {children} + + ), + img: ({ src, alt, ...rest }: any) => ( + {alt} + ), + }} > {content || ''} diff --git a/src/components/repositories/MarkdownRenderers.tsx b/src/components/repositories/MarkdownRenderers.tsx index 0f328a03..b8134f18 100644 --- a/src/components/repositories/MarkdownRenderers.tsx +++ b/src/components/repositories/MarkdownRenderers.tsx @@ -1,71 +1,24 @@ -import React from 'react'; - -export const createLinkRenderer = ( +/** + * Resolve a relative URL to an absolute GitHub URL. + * Returns the original URL if it's already absolute. + */ +export const resolveRelativeUrl = ( + url: string | undefined, repositoryFullName: string, defaultBranch: string, -) => { - const LinkRenderer = (props: any) => { - const { href, children, ...rest } = props; - let finalHref = href; - - if ( - href && - !href.startsWith('http') && - !href.startsWith('//') && - !href.startsWith('#') && - !href.startsWith('mailto:') - ) { - const cleanPath = href.startsWith('./') - ? href.slice(2) - : href.startsWith('/') - ? href.slice(1) - : href; - const hasExtension = /\.[a-zA-Z0-9]+$/.test(cleanPath.replace(/\/$/, '')); - const isDirectory = cleanPath.endsWith('/') || !hasExtension; - const type = isDirectory ? 'tree' : 'blob'; - const normalizedPath = cleanPath.replace(/\/$/, ''); - finalHref = `https://github.com/${repositoryFullName}/${type}/${defaultBranch}/${normalizedPath}`; - } - - return ( - - {children} - - ); - }; - return LinkRenderer; -}; + type: 'blob' | 'cdn' = 'blob', +): string | undefined => { + if (!url || url.startsWith('http') || url.startsWith('//') || url.startsWith('#') || url.startsWith('mailto:')) { + return url; + } -export const createImageRenderer = ( - repositoryFullName: string, - defaultBranch: string, -) => { - const ImageRenderer = (props: any) => { - const { src, alt, ...rest } = props; - let finalSrc = src; + const cleanPath = url.replace(/^\.\//, '').replace(/^\//, ''); - if (src && !src.startsWith('http') && !src.startsWith('//')) { - const cleanPath = src.startsWith('./') - ? src.slice(2) - : src.startsWith('/') - ? src.slice(1) - : src; - finalSrc = `https://cdn.jsdelivr.net/gh/${repositoryFullName}@${defaultBranch}/${cleanPath}`; - } + if (type === 'cdn') { + return `https://cdn.jsdelivr.net/gh/${repositoryFullName}@${defaultBranch}/${cleanPath}`; + } - return ( - {alt} - ); - }; - return ImageRenderer; + const hasExtension = /\.[a-zA-Z0-9]+$/.test(cleanPath.replace(/\/$/, '')); + const ghType = cleanPath.endsWith('/') || !hasExtension ? 'tree' : 'blob'; + return `https://github.com/${repositoryFullName}/${ghType}/${defaultBranch}/${cleanPath.replace(/\/$/, '')}`; }; diff --git a/src/components/repositories/ReadmeViewer.tsx b/src/components/repositories/ReadmeViewer.tsx index 7a7fa8d2..fa02da02 100644 --- a/src/components/repositories/ReadmeViewer.tsx +++ b/src/components/repositories/ReadmeViewer.tsx @@ -5,7 +5,7 @@ import remarkGfm from 'remark-gfm'; import rehypeRaw from 'rehype-raw'; import axios from 'axios'; import { STATUS_COLORS } from '../../theme'; -import { createLinkRenderer, createImageRenderer } from './MarkdownRenderers'; +import { resolveRelativeUrl } from './MarkdownRenderers'; interface ReadmeViewerProps { repositoryFullName: string; // e.g., "opentensor/bittensor" @@ -69,9 +69,6 @@ const ReadmeViewer: React.FC = ({ repositoryFullName }) => { ); } - const LinkRenderer = createLinkRenderer(repositoryFullName, defaultBranch); - const ImageRenderer = createImageRenderer(repositoryFullName, defaultBranch); - return ( = ({ repositoryFullName }) => { borderBottom: '1px solid #30363d', pb: 0.3, mb: 3, - mt: 1, // Reduced from 4 + mt: 1, fontWeight: 600, color: '#ffffff', }, @@ -99,7 +96,7 @@ const ReadmeViewer: React.FC = ({ repositoryFullName }) => { borderBottom: '1px solid #30363d', pb: 0.3, mb: 3, - mt: 2, // Reduced from 4 + mt: 2, fontWeight: 600, color: '#ffffff', }, @@ -182,8 +179,19 @@ const ReadmeViewer: React.FC = ({ repositoryFullName }) => { remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} components={{ - a: LinkRenderer, - img: ImageRenderer, + a: ({ href, children, ...rest }: any) => ( + + {children} + + ), + img: ({ src, alt, ...rest }: any) => ( + {alt} + ), }} > {content || ''} diff --git a/src/components/repositories/index.ts b/src/components/repositories/index.ts index 715b6323..a3c79d90 100644 --- a/src/components/repositories/index.ts +++ b/src/components/repositories/index.ts @@ -11,4 +11,4 @@ export { default as ReadmeViewer } from './ReadmeViewer'; export { default as RepositoryCodeBrowser } from './RepositoryCodeBrowser'; export { default as RepositoryMaintainers } from './RepositoryMaintainers'; export { default as RepositoryCheckTab } from './RepositoryCheckTab'; -export { createLinkRenderer, createImageRenderer } from './MarkdownRenderers'; +export { resolveRelativeUrl } from './MarkdownRenderers'; From 51bc7b9d6b11fdfbcee20263e8aa761b161b992b Mon Sep 17 00:00:00 2001 From: tmimmanuel <14046872+tmimmanuel@users.noreply.github.com> Date: Thu, 9 Apr 2026 18:53:34 +0200 Subject: [PATCH 6/6] style: format code with prettier --- .../repositories/ContributingViewer.tsx | 21 ++++++++++++++++--- .../repositories/MarkdownRenderers.tsx | 8 ++++++- src/components/repositories/ReadmeViewer.tsx | 21 ++++++++++++++++--- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/components/repositories/ContributingViewer.tsx b/src/components/repositories/ContributingViewer.tsx index e240f12c..01d59abd 100644 --- a/src/components/repositories/ContributingViewer.tsx +++ b/src/components/repositories/ContributingViewer.tsx @@ -178,15 +178,30 @@ const ContributingViewer: React.FC = ({ rehypePlugins={[rehypeRaw]} components={{ a: ({ href, children, ...rest }: any) => ( - + {children} ), img: ({ src, alt, ...rest }: any) => ( {alt} ), diff --git a/src/components/repositories/MarkdownRenderers.tsx b/src/components/repositories/MarkdownRenderers.tsx index b8134f18..94406f09 100644 --- a/src/components/repositories/MarkdownRenderers.tsx +++ b/src/components/repositories/MarkdownRenderers.tsx @@ -8,7 +8,13 @@ export const resolveRelativeUrl = ( defaultBranch: string, type: 'blob' | 'cdn' = 'blob', ): string | undefined => { - if (!url || url.startsWith('http') || url.startsWith('//') || url.startsWith('#') || url.startsWith('mailto:')) { + if ( + !url || + url.startsWith('http') || + url.startsWith('//') || + url.startsWith('#') || + url.startsWith('mailto:') + ) { return url; } diff --git a/src/components/repositories/ReadmeViewer.tsx b/src/components/repositories/ReadmeViewer.tsx index fa02da02..98be1556 100644 --- a/src/components/repositories/ReadmeViewer.tsx +++ b/src/components/repositories/ReadmeViewer.tsx @@ -180,15 +180,30 @@ const ReadmeViewer: React.FC = ({ repositoryFullName }) => { rehypePlugins={[rehypeRaw]} components={{ a: ({ href, children, ...rest }: any) => ( - + {children} ), img: ({ src, alt, ...rest }: any) => ( {alt} ),