Skip to content

Commit

Permalink
feat: markdown extra component
Browse files Browse the repository at this point in the history
Signed-off-by: Innei <tukon479@gmail.com>
  • Loading branch information
Innei committed Jun 20, 2023
1 parent 7a4b87f commit 525cbc0
Show file tree
Hide file tree
Showing 8 changed files with 212 additions and 19 deletions.
2 changes: 1 addition & 1 deletion src/app/notes/[id]/page.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
.paragraph .indent {
border-bottom: 1px solid;

@apply border-accent/20 dark:border-accent/40;
@apply border-accent/20 dark:border-accent/30;
}

blockquote {
Expand Down
6 changes: 3 additions & 3 deletions src/components/layout/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { HeaderContent } from './internal/HeaderContent'
import { HeaderDataConfigureProvider } from './internal/HeaderDataConfigureProvider'
import { HeaderDrawerButton } from './internal/HeaderDrawerButton'
import { HeaderMeta } from './internal/HeaderMeta'
import { HeaderWithShadow } from './internal/HeaderWithShadow'
import { SiteOwnerAvatar } from './internal/SiteOwnerAvatar'
import { UserAuth } from './internal/UserAuth'

Expand All @@ -25,10 +26,9 @@ export const Header = () => {
</HeaderDataConfigureProvider>
)
}

const MemoedHeader = memo(() => {
return (
<header className="fixed left-0 right-0 top-0 z-[9] h-[4.5rem] overflow-hidden">
<HeaderWithShadow>
<BluredBackground />
<div
className={clsxm(
Expand Down Expand Up @@ -57,7 +57,7 @@ const MemoedHeader = memo(() => {
<UserAuth />
</div>
</div>
</header>
</HeaderWithShadow>
)
})

Expand Down
19 changes: 19 additions & 0 deletions src/components/layout/header/internal/HeaderWithShadow.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
'use client'

import clsx from 'clsx'

import { usePageScrollLocationSelector } from '~/providers/root/page-scroll-info-provider'

export const HeaderWithShadow: Component = ({ children }) => {
const showShadow = usePageScrollLocationSelector((y) => y > 100)
return (
<header
className={clsx(
'fixed left-0 right-0 top-0 z-[9] h-[4.5rem] overflow-hidden transition-shadow duration-200',
showShadow && 'shadow-xl shadow-neutral-100 dark:shadow-neutral-800',
)}
>
{children}
</header>
)
}
14 changes: 6 additions & 8 deletions src/components/ui/link-card/LinkCard.module.css
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,14 @@
overflow: hidden;
margin: 16px auto;
padding: 12px 18px;

@apply cursor-pointer font-sans;

background-color: #f3f3f380;
backdrop-filter: blur(20px) saturate(180%);
transition: background-color 0.2s ease-in-out;
}
border: 0;

:global([data-theme='dark']) .card-grid {
background-color: #28282880;
@apply cursor-pointer font-sans;
@apply [&_*]:!not-italic;
@apply border border-slate-100 bg-gray-100/80 dark:border-neutral-700 dark:bg-neutral-800;
@apply transition-colors duration-200;
}

.contents {
Expand Down Expand Up @@ -55,7 +53,7 @@

.image {
@apply aspect-square flex-shrink-0 bg-cover bg-center bg-no-repeat;
@apply bg-gray-600 dark:bg-gray-50;
@apply bg-gray-50 dark:bg-neutral-700;

height: 60px;
width: 60px;
Expand Down
10 changes: 5 additions & 5 deletions src/components/ui/link-card/LinkCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,12 +163,12 @@ export const LinkCard: FC<LinkCardProps> = (props) => {
className,
)}
>
<div className={styles['contents']}>
<div className={styles['title']}>{cardInfo?.title}</div>
<div className={styles['desc']}>{cardInfo?.desc}</div>
</div>
<span className={styles['contents']}>
<span className={styles['title']}>{cardInfo?.title}</span>
<span className={styles['desc']}>{cardInfo?.desc}</span>
</span>
{(loading || cardInfo?.image) && (
<div
<span
className={styles['image']}
data-image={cardInfo?.image || ''}
style={{
Expand Down
75 changes: 73 additions & 2 deletions src/components/ui/markdown/Markdown.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
/* eslint-disable react-hooks/rules-of-hooks */
import React, { memo, useMemo, useRef } from 'react'
import React, { Fragment, memo, useMemo, useRef } from 'react'
import { clsx } from 'clsx'
import { compiler } from 'markdown-to-jsx'
import { compiler, sanitizeUrl } from 'markdown-to-jsx'
import type { MarkdownToJSX } from 'markdown-to-jsx'
import type { FC, PropsWithChildren } from 'react'

import { useElementSize } from '~/providers/article/article-element-provider'
import { isDev } from '~/utils/env'
import { springScrollToElement } from '~/utils/scroller'

import { Gallery } from '../gallery'
import { FixedZoomedImage } from '../image'
Expand All @@ -21,6 +23,7 @@ import { SpoilderRule } from './parsers/spoiler'
import { MParagraph, MTableBody, MTableHead, MTableRow } from './renderers'
import { MDetails } from './renderers/collapse'
import { MFootNote } from './renderers/footnotes'
import { MLink } from './renderers/link'

export interface MdProps {
value?: string
Expand Down Expand Up @@ -92,6 +95,74 @@ export const Markdown: FC<MdProps & MarkdownToJSX.Options & PropsWithChildren> =
},
},

link: {
react(node, output, state) {
const { target, title } = node
return (
<MLink
href={sanitizeUrl(target)!}
title={title}
key={state?.key}
>
{output(node.content, state!)}
</MLink>
)
},
},

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 (
<Fragment key={state?.key}>
<a
href={sanitizeUrl(target)!}
onClick={(e) => {
e.preventDefault()

springScrollToElement(
document.getElementById(content)!,

-window.innerHeight / 2,
)
}}
>
<sup key={state?.key}>^{content}</sup>
</a>
{linkCardId && <LinkCard id={linkCardId} source="mx-space" />}
</Fragment>
)
},
},
// codeBlock: {
// react(node, output, state) {
// return (
// <CodeBlock
// key={state?.key}
// content={node.content}
// lang={node.lang}
// />
// )
// },
// },

list: {
react(node, output, state) {
const Tag = node.ordered ? 'ol' : 'ul'
Expand Down
13 changes: 13 additions & 0 deletions src/components/ui/markdown/renderers/link.module.css
Original file line number Diff line number Diff line change
@@ -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%;
}
92 changes: 92 additions & 0 deletions src/components/ui/markdown/renderers/link.tsx
Original file line number Diff line number Diff line change
@@ -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 = () => (
<svg
style={{
transform: `translateY(-2px)`,
marginLeft: `2px`,
}}
xmlns="http://www.w3.org/2000/svg"
x="0px"
y="0px"
viewBox="0 0 100 100"
width="15"
height="15"
className="inline align-middle leading-normal"
>
<path
fill="var(--shizuku-text-color)"
d="M18.8,85.1h56l0,0c2.2,0,4-1.8,4-4v-32h-8v28h-48v-48h28v-8h-32l0,0c-2.2,0-4,1.8-4,4v56C14.8,83.3,16.6,85.1,18.8,85.1z"
/>
<polygon
fill="var(--shizuku-text-color)"
points="45.7,48.7 51.3,54.3 77.2,28.5 77.2,37.2 85.2,37.2 85.2,14.9 62.8,14.9 62.8,22.9 71.5,22.9"
/>
</svg>
)
export const MLink: FC<{
href: string
title?: string
children?: JSX.Element | JSX.Element[]
}> = memo((props) => {
const handleRedirect = useCallback(
(e: React.MouseEvent<HTMLAnchorElement, 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 (
<FloatPopover
as="span"
wrapperClassNames="!inline"
TriggerComponent={() => (
<>
<a
className={styles['anchor']}
href={props.href}
target="_blank"
onClick={handleRedirect}
title={props.title}
>
{props.children}
</a>

{ExtendIcon}
</>
)}
>
<span>{props.href}</span>
</FloatPopover>
)
})

1 comment on commit 525cbc0

@vercel
Copy link

@vercel vercel bot commented on 525cbc0 Jun 20, 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:

springtide – ./

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

Please sign in to comment.