=
},
},
+ link: {
+ react(node, output, state) {
+ const { target, title } = node
+ return (
+
+ {output(node.content, state!)}
+
+ )
+ },
+ },
+
+ footnoteReference: {
+ react(node, output, state) {
+ const { footnoteMap, target, content } = node
+ const footnote = footnoteMap.get(content)
+ const linkCardId = (() => {
+ try {
+ const thisUrl = new URL(footnote?.footnote?.replace(': ', ''))
+ const isCurrentHost =
+ thisUrl.hostname === window.location.hostname
+
+ if (!isCurrentHost && !isDev) {
+ return undefined
+ }
+ const pathname = thisUrl.pathname
+ return pathname.slice(1)
+ } catch {
+ return undefined
+ }
+ })()
+
+ return (
+
+ {
+ e.preventDefault()
+
+ springScrollToElement(
+ document.getElementById(content)!,
+
+ -window.innerHeight / 2,
+ )
+ }}
+ >
+ ^{content}
+
+ {linkCardId && }
+
+ )
+ },
+ },
+ // codeBlock: {
+ // react(node, output, state) {
+ // return (
+ //
+ // )
+ // },
+ // },
+
list: {
react(node, output, state) {
const Tag = node.ordered ? 'ol' : 'ul'
diff --git a/src/components/ui/markdown/renderers/link.module.css b/src/components/ui/markdown/renderers/link.module.css
new file mode 100644
index 0000000000..6dd053fdd6
--- /dev/null
+++ b/src/components/ui/markdown/renderers/link.module.css
@@ -0,0 +1,13 @@
+.anchor {
+ @apply relative inline cursor-alias overflow-hidden text-accent;
+}
+
+.anchor::after {
+ @apply absolute -bottom-0.5 left-1/2 w-0 -translate-x-1/2 border-b-[1px] border-current text-center transition-[width] duration-500 ease-in-out;
+
+ content: '';
+}
+
+.anchor:hover::after {
+ width: 100%;
+}
diff --git a/src/components/ui/markdown/renderers/link.tsx b/src/components/ui/markdown/renderers/link.tsx
new file mode 100644
index 0000000000..e7b73c42de
--- /dev/null
+++ b/src/components/ui/markdown/renderers/link.tsx
@@ -0,0 +1,92 @@
+import { memo, useCallback } from 'react'
+import Router from 'next/router'
+import type { FC } from 'react'
+
+import { FloatPopover } from '../../float-popover'
+import styles from './link.module.css'
+
+const ExtendIcon = () => (
+
+)
+export const MLink: FC<{
+ href: string
+ title?: string
+ children?: JSX.Element | JSX.Element[]
+}> = memo((props) => {
+ const handleRedirect = useCallback(
+ (e: React.MouseEvent
) => {
+ const href = props.href
+ const locateUrl = new URL(location.href)
+
+ const toUrlParser = new URL(href)
+
+ if (
+ toUrlParser.host === locateUrl.host ||
+ (process.env.NODE_ENV === 'development' &&
+ toUrlParser.host === 'innei.ren')
+ ) {
+ e.preventDefault()
+ const pathArr = toUrlParser.pathname.split('/').filter(Boolean)
+ const headPath = pathArr[0]
+
+ switch (headPath) {
+ case 'posts':
+ case 'notes':
+ case 'category': {
+ Router.push(toUrlParser.pathname)
+ break
+ }
+ default: {
+ window.open(toUrlParser.pathname)
+ }
+ }
+ }
+ },
+ [props.href],
+ )
+
+ return (
+ (
+ <>
+
+ {props.children}
+
+
+ {ExtendIcon}
+ >
+ )}
+ >
+ {props.href}
+
+ )
+})