Skip to content

Commit

Permalink
feat: parse github pr link
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <[email protected]>
  • Loading branch information
Innei committed Nov 2, 2023
1 parent 8a43cdb commit 50ddd75
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 69 deletions.
53 changes: 44 additions & 9 deletions src/components/ui/link-card/LinkCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { apiClient } from '~/lib/request'

import styles from './LinkCard.module.css'

export type LinkCardSource = 'gh' | 'self' | 'mx-space' | 'gh-commit'
export type LinkCardSource = 'gh' | 'self' | 'mx-space' | 'gh-commit' | 'gh-pr'
export interface LinkCardProps {
id: string
source?: LinkCardSource
Expand Down Expand Up @@ -174,17 +174,12 @@ const LinkCardImpl: FC<LinkCardProps> = (props) => {
setCardInfo({
image: data.author.avatarUrl,
title: (
<span className="space-x-2">
<span>
{namespace}/{repo}
</span>
<span className="font-normal">
{data.commit.message.replace(/Signed-off-by:.+/, '')}
</span>
<span className="font-normal">
{data.commit.message.replace(/Signed-off-by:.+/, '')}
</span>
),
desc: (
<span className="space-x-5 font-mono">
<span className="flex items-center space-x-5 font-mono">
<span className="text-uk-green-light">
+{data.stats.additions}
</span>
Expand All @@ -193,6 +188,46 @@ const LinkCardImpl: FC<LinkCardProps> = (props) => {
</span>

<span className="text-sm">{data.sha.slice(0, 7)}</span>

<span className="text-sm opacity-80">
{namespace}/{repo}
</span>
</span>
),
})
setFullUrl(data.htmlUrl)
}

return true
}

case 'gh-pr': {
// ${owner}/${repo}/${pr}
const [owner, repo, pr] = id.split('/')
if (!owner || !repo || !pr) {
return false
}
fetchFnRef.current = async () => {
const data = await fetchGitHubApi<any>(
`https://api.github.com/repos/${owner}/${repo}/pulls/${pr}`,
)
.then((data) => camelcaseKeys(data))
.catch(() => {
// set fallback url
//
setFullUrl(`https://github.com/${owner}/${repo}/commit/${pr}`)
})

setCardInfo({
image: data.user.avatarUrl,
title: <span>{data.title}</span>,
desc: (
<span className="flex items-center space-x-5 font-mono">
<span className="text-uk-green-light">+{data.additions}</span>
<span className="text-uk-red-light">-{data.deletions}</span>
<span className="text-sm opacity-80">
{owner}/{repo}
</span>
</span>
),
})
Expand Down
132 changes: 80 additions & 52 deletions src/components/ui/markdown/renderers/LinkRenderer.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import React, { useMemo } from 'react'
import dynamic from 'next/dynamic'
import type { PropsWithChildren } from 'react'
import type { FC, PropsWithChildren, ReactNode } from 'react'

import { GitHubBrandIcon } from '~/components/icons/platform/GitHubBrandIcon'
import {
Expand All @@ -9,12 +9,13 @@ import {
isGistUrl,
isGithubCommitUrl,
isGithubFilePreviewUrl,
isGithubRepoUrl,
isGithubPrUrl,
isGithubUrl,
isSelfArticleUrl,
isTweetUrl,
isYoutubeUrl,
parseGithubGistUrl,
parseGithubRepoUrl,
parseGithubPrUrl,
parseGithubTypedUrl,
} from '~/lib/link-parser'

Expand All @@ -25,7 +26,11 @@ import { MLink } from '../../link/MLink'
const Tweet = dynamic(() => import('~/components/widgets/shared/Tweet'), {
ssr: false,
})
export const LinkRenderer = ({

/**
* 单行链接的渲染
*/
export const BlockLinkRenderer = ({
href,
children,
}: PropsWithChildren<{ href: string }>) => {
Expand All @@ -49,18 +54,23 @@ export const LinkRenderer = ({
if (!url) {
return fallbackElement
}

switch (true) {
case isGithubUrl(url): {
return (
<GithubUrlRenderL
url={url}
href={href}
fallbackElement={fallbackElement}
/>
)
}
case isTweetUrl(url): {
const id = getTweetId(url)

return <Tweet id={id} />
}

case isGithubRepoUrl(url): {
const { owner, repo } = parseGithubRepoUrl(url)
return <LinkCard id={`${owner}/${repo}`} source="gh" />
}

case isYoutubeUrl(url): {
const id = url.searchParams.get('v')!
return (
Expand All @@ -75,6 +85,62 @@ export const LinkRenderer = ({
</FixedRatioContainer>
)
}

case isCodesandboxUrl(url): {
// https://codesandbox.io/s/framer-motion-layoutroot-prop-forked-p39g96
// to
// https://codesandbox.io/embed/framer-motion-layoutroot-prop-forked-p39g96?fontsize=14&hidenavigation=1&theme=dark
return (
<FixedRatioContainer>
<iframe
className="absolute inset-0 h-full w-full rounded-md border-0"
src={`https://codesandbox.io/embed/${url.pathname.slice(
2,
)}?fontsize=14&hidenavigation=1&theme=dark${url.search}`}
/>
</FixedRatioContainer>
)
}
case isSelfArticleUrl(url): {
return <LinkCard source="self" id={url.pathname.slice(1)} />
}

default:
return fallbackElement
}
}

const FixedRatioContainer = ({
children,
ratio = 58,
}: {
ratio?: number
children: React.ReactNode
}) => {
return (
<div className="mockup-window my-16 bg-base-300">
<div className="flex justify-center px-4">
<div
className="relative my-8 h-0 w-full"
style={{
paddingBottom: `${ratio}%`,
}}
>
{children}
</div>
</div>
</div>
)
}

const GithubUrlRenderL: FC<{
url: URL
href?: string
fallbackElement: ReactNode
}> = (props) => {
const { url, href = url.href, fallbackElement } = props
console.log('url', url.toString(), isGithubPrUrl(url))
switch (true) {
case isGistUrl(url): {
const { owner, id } = parseGithubGistUrl(url)
return (
Expand All @@ -96,6 +162,10 @@ export const LinkRenderer = ({
</>
)
}
case isGithubPrUrl(url): {
const { owner, repo, pr } = parseGithubPrUrl(url)
return <LinkCard id={`${owner}/${repo}/${pr}`} source="gh-pr" />
}

case isGithubCommitUrl(url): {
const { owner, repo, id } = parseGithubTypedUrl(url)
Expand Down Expand Up @@ -125,49 +195,7 @@ export const LinkRenderer = ({
</>
)
}
case isCodesandboxUrl(url): {
// https://codesandbox.io/s/framer-motion-layoutroot-prop-forked-p39g96
// to
// https://codesandbox.io/embed/framer-motion-layoutroot-prop-forked-p39g96?fontsize=14&hidenavigation=1&theme=dark
return (
<FixedRatioContainer>
<iframe
className="absolute inset-0 h-full w-full rounded-md border-0"
src={`https://codesandbox.io/embed/${url.pathname.slice(
2,
)}?fontsize=14&hidenavigation=1&theme=dark${url.search}`}
/>
</FixedRatioContainer>
)
}
case isSelfArticleUrl(url): {
return <LinkCard source="self" id={url.pathname.slice(1)} />
}

default:
return fallbackElement
}
}

const FixedRatioContainer = ({
children,
ratio = 58,
}: {
ratio?: number
children: React.ReactNode
}) => {
return (
<div className="mockup-window my-16 bg-base-300">
<div className="flex justify-center px-4">
<div
className="relative my-8 h-0 w-full"
style={{
paddingBottom: `${ratio}%`,
}}
>
{children}
</div>
</div>
</div>
)
return fallbackElement
}
6 changes: 3 additions & 3 deletions src/components/ui/markdown/renderers/paragraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react'
import clsx from 'clsx'
import type { DetailedHTMLProps, FC, HTMLAttributes, ReactNode } from 'react'

import { LinkRenderer } from './LinkRenderer'
import { BlockLinkRenderer } from './LinkRenderer'

export const MParagraph: FC<
DetailedHTMLProps<HTMLAttributes<HTMLParagraphElement>, HTMLParagraphElement>
Expand All @@ -21,9 +21,9 @@ export const MParagraph: FC<
if (isLink(child)) {
const children = (child as any)?.props?.children as ReactNode[]
return (
<LinkRenderer href={(child as any)?.props?.href}>
<BlockLinkRenderer href={(child as any)?.props?.href}>
{children}
</LinkRenderer>
</BlockLinkRenderer>
)
}
}
Expand Down
26 changes: 21 additions & 5 deletions src/lib/link-parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,20 @@ export const getTweetId = (url: URL) => {
return url.pathname.split('/').pop()!
}

const GITHUB_HOST = 'github.com'

export const isGithubRepoUrl = (url: URL) => {
return (
url.hostname === 'github.com' &&
url.hostname === GITHUB_HOST &&
url.pathname.startsWith('/') &&
url.pathname.split('/').length === 3
)
}

export const isGithubPrUrl = (url: URL) => {
return url.hostname === GITHUB_HOST && url.pathname.includes('/pull/')
}

export const isYoutubeUrl = (url: URL) => {
return url.hostname === 'www.youtube.com' && url.pathname.startsWith('/watch')
}
Expand All @@ -22,17 +28,17 @@ export const isGistUrl = (url: URL) => {

export const isGithubCommitUrl = (url: URL) => {
const [_, , , type] = url.pathname.split('/')
return url.hostname === 'github.com' && type === 'commit'
return url.hostname === GITHUB_HOST && type === 'commit'
}

export const isGithubProfileUrl = (url: URL) => {
return url.hostname === 'github.com' && url.pathname.split('/').length === 2
return url.hostname === GITHUB_HOST && url.pathname.split('/').length === 2
}

export const isGithubFilePreviewUrl = (url: URL) => {
// https://github.com/Innei/sprightly/blob/14234594f44956e6f56f1f92952ce82db37ef4df/src/socket/handler.ts
const [_, , , type] = url.pathname.split('/')
return url.hostname === 'github.com' && type === 'blob'
return url.hostname === GITHUB_HOST && type === 'blob'
}

export const isTweetUrl = (url: URL) => {
Expand All @@ -44,7 +50,7 @@ export const isTwitterProfileUrl = (url: URL) => {
}

export const isGithubUrl = (url: URL) => {
return url.hostname === 'github.com'
return url.hostname === GITHUB_HOST
}

export const isTwitterUrl = (url: URL) => {
Expand Down Expand Up @@ -144,3 +150,13 @@ export const parseZhihuProfileUrl = (url: URL) => {
id,
}
}

export const parseGithubPrUrl = (url: URL) => {
const [_, owner, repo, type, pr] = url.pathname.split('/')
return {
owner,
repo,
type,
pr,
}
}

0 comments on commit 50ddd75

Please sign in to comment.