Skip to content

Commit

Permalink
feat: rich link
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <i@innei.in>
  • Loading branch information
Innei committed Jul 8, 2023
1 parent 2c69af6 commit 52cd236
Show file tree
Hide file tree
Showing 10 changed files with 388 additions and 109 deletions.
82 changes: 82 additions & 0 deletions src/components/ui/markdown/customize.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
## Rich Link

```
https://github.com/Innei/Shiro
```

https://github.com/Innei/Shiro

```
https://twitter.com/zhizijun/status/1649822091234148352?s=20
```

https://twitter.com/zhizijun/status/1649822091234148352?s=20

```
https://www.youtube.com/watch?v=N93cTbtLCIM
```

https://www.youtube.com/watch?v=N93cTbtLCIM

```
https://gist.github.com/Innei/94b3e8f078d29e1820813a24a3d8b04e
```

https://gist.github.com/Innei/94b3e8f078d29e1820813a24a3d8b04e

```
https://github.com/vuejs/vitepress/commit/71eb11f72e60706a546b756dc3fd72d06e2ae4e2
```

https://github.com/vuejs/vitepress/commit/71eb11f72e60706a546b756dc3fd72d06e2ae4e2

## LinkCard

```
<LinkCard source="gh" id="mx-space/kami">
```

<LinkCard source="gh" id="mx-space/kami">


```
<LinkCard source="gh-commit" id="mx-space/kami/commit/e1eee4136c21ab03ab5690e17025777984c362a0">
```

<LinkCard source="gh-commit" id="mx-space/kami/commit/e1eee4136c21ab03ab5690e17025777984c362a0">

## Inline Link Parser

```
Inline [Innei](https://github.com/Innei)
```

Inline [Innei](https://github.com/Innei)

```
Inline [pseudoyu](https://twitter.com/pseudo_yu)
```

Inline [pseudoyu](https://twitter.com/pseudo_yu)


```
Inline <https://github.com/Innei>
```

Inline <https://github.com/Innei>

```
Inline https://github.com/Innei
```

Inline https://github.com/Innei


## Mention

```
[Innei]{GH@Innei}
```

[Innei 太菜了]{GH@Innei}
45 changes: 39 additions & 6 deletions src/components/ui/markdown/index.demo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,55 @@ import { ToastContainer } from 'react-toastify'
import { ThemeProvider } from 'next-themes'
import type { DocumentComponent } from 'storybook/typings'

import { Markdown } from './Markdown'
// @ts-expect-error
import md from './test-text.md?raw'
import customize from './customize.md?raw'
import { Markdown } from './Markdown'

export const MarkdownDemo1: DocumentComponent = () => {
export const MarkdownCustomize: DocumentComponent = () => {
return (
<ThemeProvider>
<main className="relative m-auto mt-6 max-w-[800px] border border-accent/10">
<Markdown value={md} className="prose" as="article" />
<Markdown
extendsRules={{
codeBlock: {
react(node, output, state) {
return (
<pre>
<code>{node.content}</code>
</pre>
)
},
},
}}
value={customize}
className="prose"
as="article"
/>
</main>

<ToastContainer />
</ThemeProvider>
)
}

MarkdownDemo1.meta = {
title: 'Markdown',
MarkdownCustomize.meta = {
title: 'Markdown Customize',
}

// export const MarkdownCommon: DocumentComponent = () => {
// return (
// <LazyLoad>
// <ThemeProvider>
// <main className="relative m-auto mt-6 max-w-[800px] border border-accent/10">
// <Markdown value={md} className="prose" as="article" />
// </main>

// <ToastContainer />
// </ThemeProvider>
// </LazyLoad>
// )
// }

// MarkdownCommon.meta = {
// title: 'Markdown Common',
// }
40 changes: 3 additions & 37 deletions src/components/ui/markdown/parsers/mention.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,9 @@ import React from 'react'
import { Priority, simpleInlineRegex } from 'markdown-to-jsx'
import type { MarkdownToJSX } from 'markdown-to-jsx'

import {
CodiconGithubInverted,
IcBaselineTelegram,
MdiTwitter,
} from '~/components/icons/menu-collection'
import { RichLink } from '../../rich-link/RichLink'

const prefixToIconMap = {
GH: <CodiconGithubInverted className="text-[#1D2127] dark:text-[#FFFFFF]" />,
TW: <MdiTwitter className="text-[#1DA1F2]" />,
TG: <IcBaselineTelegram className="text-[#2AABEE]" />,
}

const prefixToUrlMap = {
GH: 'https://github.com/',
TW: 'https://twitter.com/',
TG: 'https://t.me/',
}

// {GH@Innei} {TW@Innei} {TG@Innei}
// [Innei]{GH@Innei} {TW@Innei} {TG@Innei}
export const MentionRule: MarkdownToJSX.Rule = {
match: simpleInlineRegex(
/^(\[(?<displayName>.*?)\])?\{((?<prefix>(GH)|(TW)|(TG))@(?<name>\w+\b))\}\s?(?!\[.*?\])/,
Expand Down Expand Up @@ -48,26 +32,8 @@ export const MentionRule: MarkdownToJSX.Rule = {
return null as any
}

// @ts-ignore
const Icon = prefixToIconMap[prefix]
// @ts-ignore
const urlPrefix = prefixToUrlMap[prefix]

return (
<span
className="mx-1 inline-flex items-center space-x-1 align-bottom"
key={state?.key}
>
{Icon}
<a
target="_blank"
rel="noreferrer nofollow"
href={`${urlPrefix}${name}`}
className="underline-offset-2"
>
{displayName || name}
</a>
</span>
<RichLink name={displayName || name} source={prefix} key={state?.key} />
)
},
}
57 changes: 19 additions & 38 deletions src/components/ui/markdown/renderers/LinkRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@ import React, { useMemo } from 'react'
import dynamic from 'next/dynamic'

import { GitHubBrandIcon } from '~/components/icons/platform/GitHubBrandIcon'
import {
getTweetId,
isGistUrl,
isGithubCommitUrl,
isGithubRepoUrl,
isTweetUrl,
isYoutubeUrl,
parseGithubCommitUrl,
parseGithubGistUrl,
parseGithubRepoUrl,
} from '~/lib/link-parser'

import { LinkCard } from '../../link-card'
import { MLink } from './link'
Expand All @@ -27,7 +38,7 @@ export const LinkRenderer = ({ href }: { href: string }) => {
}

case isGithubRepoUrl(url): {
const [_, owner, repo] = url.pathname.split('/')
const { owner, repo } = parseGithubRepoUrl(url)
return <LinkCard id={`${owner}/${repo}`} source="gh" />
}

Expand All @@ -46,18 +57,16 @@ export const LinkRenderer = ({ href }: { href: string }) => {
)
}
case isGistUrl(url): {
const [_, owner, id] = url.pathname.split('/')
const { owner, id } = parseGithubGistUrl(url)
return (
<>
<FixedRatioContainer>
<iframe
src={`https://gist.github.com/${owner}/${id}.pibb`}
className="absolute inset-0 h-full w-full border-0"
/>
</FixedRatioContainer>
<iframe
src={`https://gist.github.com/${owner}/${id}.pibb`}
className="max-h-[300px] w-full overflow-auto border-0"
/>

<a
className="-mt-4 mb-4 flex space-x-2 center"
className="mt-2 flex space-x-2 center"
href={href}
target="_blank"
rel="noreferrer"
Expand All @@ -70,7 +79,7 @@ export const LinkRenderer = ({ href }: { href: string }) => {
}

case isGithubCommitUrl(url): {
const [_, owner, repo, type, id] = url.pathname.split('/')
const { owner, repo, id } = parseGithubCommitUrl(url)
return (
<>
<p>
Expand Down Expand Up @@ -110,31 +119,3 @@ const FixedRatioContainer = ({
</div>
)
}

const isTweetUrl = (url: URL) => {
return url.hostname === 'twitter.com' && url.pathname.startsWith('/')
}
const getTweetId = (url: URL) => {
return url.pathname.split('/').pop()!
}

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

const isYoutubeUrl = (url: URL) => {
return url.hostname === 'www.youtube.com' && url.pathname.startsWith('/watch')
}

const isGistUrl = (url: URL) => {
return url.hostname === 'gist.github.com'
}

const isGithubCommitUrl = (url: URL) => {
const [_, owner, repo, type, ...rest] = url.pathname.split('/')
return url.hostname === 'github.com' && type === 'commit'
}
67 changes: 52 additions & 15 deletions src/components/ui/markdown/renderers/link.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,23 @@ import { memo, useCallback } from 'react'
import Router from 'next/router'
import type { FC, ReactNode } from 'react'

import {
isGithubProfileUrl,
isTelegramUrl,
isTwitterProfileUrl,
} from '~/lib/link-parser'

import { FloatPopover } from '../../float-popover'
import { Favicon } from '../../rich-link/Favicon'
import { RichLink } from '../../rich-link/RichLink'

export const MLink: FC<{
href: string
title?: string
children?: ReactNode
}> = memo((props) => {
}> = memo(({ href, children, title }) => {
const handleRedirect = useCallback(
(e: React.MouseEvent<HTMLAnchorElement, MouseEvent>) => {
const href = props.href
const locateUrl = new URL(location.href)

const toUrlParser = new URL(href)
Expand All @@ -38,9 +45,34 @@ export const MLink: FC<{
}
}
},
[props.href],
[href],
)

let parsedType = ''
let parsedName = ''
try {
const url = new URL(href)
switch (true) {
case isGithubProfileUrl(url): {
parsedType = 'GH'
parsedName = url.pathname.split('/')[1]
break
}
case isTwitterProfileUrl(url): {
parsedType = 'TW'
parsedName = url.pathname.split('/')[1]
break
}
case isTelegramUrl(url): {
parsedType = 'TG'
parsedName = url.pathname.split('/')[1]
break
}
}
} catch {}

const showRichLink = !!parsedType && !!parsedName

return (
<FloatPopover
as="span"
Expand All @@ -49,24 +81,29 @@ export const MLink: FC<{
TriggerComponent={useCallback(
() => (
<span className="inline-flex items-center">
<a
className="shiro-link--underline"
href={props.href}
target="_blank"
onClick={handleRedirect}
title={props.title}
rel="noreferrer"
>
{props.children}
</a>
{!showRichLink && <Favicon href={href} />}
{showRichLink ? (
<RichLink name={parsedName} source={parsedType} />
) : (
<a
className="shiro-link--underline"
href={href}
target="_blank"
onClick={handleRedirect}
title={title}
rel="noreferrer"
>
{children}
</a>
)}

<i className="icon-[mingcute--external-link-line]" />
</span>
),
[handleRedirect, props.children, props.href, props.title],
[handleRedirect, children, href, title],
)}
>
<span>{props.href}</span>
<span>{href}</span>
</FloatPopover>
)
})
Expand Down
Loading

1 comment on commit 52cd236

@vercel
Copy link

@vercel vercel bot commented on 52cd236 Jul 8, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

shiro – ./

shiro-git-main-innei.vercel.app
innei.in
shiro-innei.vercel.app
springtide.vercel.app

Please sign in to comment.