diff --git a/src/components/repo/RepoReadme.tsx b/src/components/repo/RepoReadme.tsx index 35968f9..953797d 100644 --- a/src/components/repo/RepoReadme.tsx +++ b/src/components/repo/RepoReadme.tsx @@ -1,20 +1,52 @@ +import type { Handle } from '@atcute/lexicons' import type { $output as RepoTreeResponse } from '@atcute/tangled/types/repo/tree' +import { defaultUrlTransform, type UrlTransform } from 'react-markdown' import { MarkdownContent } from '../shared/MarkdownContent' import { SurfaceCard } from '../shared/SurfaceCard' +const URL_SCHEME_PATTERN = /^[a-z][a-z\d+.-]*:/i + type RepoReadmeProps = { readme: NonNullable | undefined + repositoryOwner: Handle + repositoryName: string + repositoryRef: string } -export function RepoReadme({ readme }: RepoReadmeProps) { +export function RepoReadme({ + readme, + repositoryOwner, + repositoryName, + repositoryRef, +}: RepoReadmeProps) { if (!readme?.contents.trim()) return null + const urlTransform = createRepositoryUrlTransform(repositoryOwner, repositoryName, repositoryRef) return (

{readme.filename}

- + {readme.contents}
) } + +function createRepositoryUrlTransform( + repositoryOwner: Handle, + repositoryName: string, + repositoryRef: string, +): UrlTransform { + const rawFileBaseUrl = `https://tangled.org/${encodeURIComponent(repositoryOwner)}/${encodeURIComponent(repositoryName)}/raw/${encodeURIComponent(repositoryRef)}/` + + return (url) => { + const safeUrl = defaultUrlTransform(url) + if (!isRepositoryRelativeUrl(safeUrl)) return safeUrl + + return new URL(safeUrl, rawFileBaseUrl).toString() + } +} + +function isRepositoryRelativeUrl(url: string): boolean { + return !url.startsWith('#') && !url.startsWith('/') && !URL_SCHEME_PATTERN.test(url) +} diff --git a/src/components/shared/MarkdownContent.tsx b/src/components/shared/MarkdownContent.tsx index 90da0c6..8e02a23 100644 --- a/src/components/shared/MarkdownContent.tsx +++ b/src/components/shared/MarkdownContent.tsx @@ -1,16 +1,19 @@ -import Markdown from 'react-markdown' +import Markdown, { type UrlTransform } from 'react-markdown' import rehypeRaw from 'rehype-raw' import rehypeSanitize from 'rehype-sanitize' type MarkdownContentProps = { children: string className?: string + urlTransform?: UrlTransform } -export function MarkdownContent({ children, className = '' }: MarkdownContentProps) { +export function MarkdownContent({ children, className = '', urlTransform }: MarkdownContentProps) { return (
- {children} + + {children} +
) } diff --git a/src/pages/RepoPage.tsx b/src/pages/RepoPage.tsx index ce97d53..c4491d6 100644 --- a/src/pages/RepoPage.tsx +++ b/src/pages/RepoPage.tsx @@ -78,7 +78,14 @@ export function RepoPage() { repoKey={getRepoRkey(repo)} /> - {activeSection === 'readme' && } + {activeSection === 'readme' && ( + + )} {shouldRenderWorkspace && (